Skip to content

Commit

Permalink
Merge pull request #885 from ostelco/feature/refactor-appnotify
Browse files Browse the repository at this point in the history
Refactors application notifier by moving the building of the notification message to one place
  • Loading branch information
Kjell M. Myksvoll authored Oct 22, 2019
2 parents 746ee42 + 1b11fce commit ed599a4
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,76 +9,102 @@ import com.google.firebase.messaging.FirebaseMessagingException
import com.google.firebase.messaging.Message
import com.google.firebase.messaging.Notification
import org.ostelco.prime.getLogger
import org.ostelco.prime.model.ApplicationToken
import org.ostelco.prime.model.FCMStrings
import org.ostelco.prime.module.getResource
import org.ostelco.prime.storage.ClientDataSource

class FirebaseAppNotifier: AppNotifier {
private val logger by getLogger()

val listOfFailureCodes = listOf(
/* Ref. to Firebase. */
private val store = getResource<ClientDataSource>()

/* Firebase messaging failure cases. */
private val listOfFailureCodes = listOf(
"messaging/invalid-recipient",
"messaging/invalid-registration-token",
"messaging/registration-token-not-registered"
)

override fun notify(customerId: String, title: String, body: String) {
logger.info("Will try to notify customer with Id : $customerId")
sendNotification(customerId, title, body, data = null)
}
override fun notify(notificationType: NotificationType, customerId: String, data: Map<String, Any>) =
when (notificationType) {
NotificationType.JUMIO_VERIFICATION_SUCCEEDED -> {
logger.info("Notifying customer $customerId of successful JUMIO verification")
sendMessage(customerId = customerId,
title = FCMStrings.JUMIO_NOTIFICATION_TITLE.s,
body = FCMStrings.JUMIO_IDENTITY_VERIFIED.s,
data = data)
}
NotificationType.JUMIO_VERIFICATION_FAILED -> {
logger.info("Notifying customer $customerId of failed JUMIO verification " +
"with data $data")
sendMessage(customerId = customerId,
title = FCMStrings.JUMIO_NOTIFICATION_TITLE.s,
body = FCMStrings.JUMIO_IDENTITY_FAILED.s,
data = data)
}
}

override fun notify(customerId: String, title: String, body: String, data: Map<String, String>) {
logger.info("Will try to notify-with-data customer with Id : $customerId $body, $data")
sendNotification(customerId, title, body, data)
override fun notify(customerId: String, title: String, body: String, data: Map<String, Any>) {
logger.info("Notifying customer $customerId of message $body with data $data")
sendMessage(customerId, title, body, data)
}

private fun sendNotification(customerId: String, title: String, body: String, data: Map<String, String>?) {

val store = getResource<ClientDataSource>()

// This registration token comes from the client FCM SDKs.
val applicationTokens = store.getNotificationTokens(customerId)

for (applicationToken in applicationTokens) {
private fun sendMessage(customerId: String, title: String, body: String, data: Map<String, Any>) =
store.getNotificationTokens(customerId)
.filter {
it.tokenType == "FCM"
}
.forEach {
sendMessage(
customerId = customerId,
token = it,
message = Message.builder()
.setNotification(
Notification(title, body))
.setToken(it.token)
.putAllData(data.mapValues {
it.value.toString()
})
.build()
)
}

if (applicationToken.tokenType == "FCM") {
// See documentation on defining a message payload.
val builder = Message.builder()
.setNotification(Notification(title, body))
.setToken(applicationToken.token)
if (data != null) {
builder.putAllData(data)
/* Send a message asynchrounously to the device corresponding to
the provided registration token. */
private fun sendMessage(customerId: String,
token: ApplicationToken,
message: Message) {
val future = FirebaseMessaging
.getInstance(FirebaseApp.getInstance("fcm"))
.sendAsync(message)
val apiFutureCallback = object : ApiFutureCallback<String> {
override fun onSuccess(result: String) {
logger.info("Notification for $customerId with appId: ${token.applicationID} " +
"completed with result: $result")
if (listOfFailureCodes.contains(result)) {
store.removeNotificationToken(customerId, token.applicationID)
}
val message = builder.build()

// Send a message to the device corresponding to the provided
// registration token.
val future = FirebaseMessaging
.getInstance(FirebaseApp.getInstance("fcm"))
.sendAsync(message)

val apiFutureCallback = object : ApiFutureCallback<String> {
override fun onSuccess(result: String) {
logger.info("Notification for $customerId with appId: ${applicationToken.applicationID} completed with result: $result")
if (listOfFailureCodes.contains(result)) {
store.removeNotificationToken(customerId, applicationToken.applicationID)
}
}
}

override fun onFailure(t: Throwable) {
if (t is FirebaseMessagingException) {
val errorCode = t.errorCode
logger.warn("Notification for $customerId with appId: ${applicationToken.applicationID} failed with errorCode: $errorCode")
if (listOfFailureCodes.contains(errorCode)) {
logger.warn("Removing failed token for $customerId with appId: ${applicationToken.applicationID} token: $applicationToken.token")
store.removeNotificationToken(customerId, applicationToken.applicationID)
}
} else {
logger.warn("Notification for $customerId with appId: ${applicationToken.applicationID} failed with error: $t")
}
override fun onFailure(t: Throwable) {
if (t is FirebaseMessagingException) {
val errorCode = t.errorCode
logger.warn("Notification for $customerId with appId: ${token.applicationID} " +
"failed with errorCode: $errorCode")
if (listOfFailureCodes.contains(errorCode)) {
logger.warn("Removing failed token for $customerId with appId: ${token.applicationID} " +
"token: $token.token")
store.removeNotificationToken(customerId, token.applicationID)
}
} else {
logger.warn("Notification for $customerId with appId: ${token.applicationID} " +
"failed with error: $t")
}
addCallback(future, apiFutureCallback, directExecutor())
}
}

addCallback(future, apiFutureCallback, directExecutor())
}
}
2 changes: 1 addition & 1 deletion model/src/main/kotlin/org/ostelco/prime/model/Entities.kt
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ enum class VendorScanData(val s: String) {
}

enum class FCMStrings(val s: String) {
NOTIFICATION_TITLE("eKYC Status"),
JUMIO_NOTIFICATION_TITLE("eKYC Status"),
JUMIO_IDENTITY_VERIFIED("Successfully verified the identity"),
JUMIO_IDENTITY_FAILED("Failed to verify the identity")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import arrow.instances.either.monad.monad
import org.neo4j.driver.v1.Transaction
import org.ostelco.prime.analytics.AnalyticsService
import org.ostelco.prime.appnotifier.AppNotifier
import org.ostelco.prime.appnotifier.NotificationType
import org.ostelco.prime.auditlog.AuditLog
import org.ostelco.prime.dsl.ReadTransaction
import org.ostelco.prime.dsl.WriteTransaction
Expand Down Expand Up @@ -43,7 +44,6 @@ import org.ostelco.prime.model.CustomerRegionStatus
import org.ostelco.prime.model.CustomerRegionStatus.APPROVED
import org.ostelco.prime.model.CustomerRegionStatus.AVAILABLE
import org.ostelco.prime.model.CustomerRegionStatus.PENDING
import org.ostelco.prime.model.FCMStrings
import org.ostelco.prime.model.HasId
import org.ostelco.prime.model.KycStatus
import org.ostelco.prime.model.KycStatus.REJECTED
Expand Down Expand Up @@ -1661,9 +1661,8 @@ object Neo4jStoreSingleton : GraphStore {
scanInformationDatastore.upsertVendorScanInformation(customer.id, scanInformation.countryCode, vendorData)
.flatMap {
appNotifier.notify(
notificationType = NotificationType.JUMIO_VERIFICATION_SUCCEEDED,
customerId = customer.id,
title = FCMStrings.NOTIFICATION_TITLE.s,
body = FCMStrings.JUMIO_IDENTITY_VERIFIED.s,
data = extendedStatus
)
logger.info(NOTIFY_OPS_MARKER, "Jumio verification succeeded for ${customer.contactEmail} Info: $extendedStatus")
Expand All @@ -1676,9 +1675,8 @@ object Neo4jStoreSingleton : GraphStore {
} else {
// TODO: find out what more information can be passed to the client.
appNotifier.notify(
notificationType = NotificationType.JUMIO_VERIFICATION_FAILED,
customerId = customer.id,
title = FCMStrings.NOTIFICATION_TITLE.s,
body = FCMStrings.JUMIO_IDENTITY_FAILED.s,
data = extendedStatus
)
logger.info(NOTIFY_OPS_MARKER, "Jumio verification failed for ${customer.contactEmail} Info: $extendedStatus")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
package org.ostelco.prime.appnotifier

/* Prime specific notification types. */
enum class NotificationType {
JUMIO_VERIFICATION_SUCCEEDED,
JUMIO_VERIFICATION_FAILED,
}

interface AppNotifier {
fun notify(customerId: String, title: String, body: String)
fun notify(customerId: String, title: String, body: String, data: Map<String, String>)

/**
* Prime specific interface for sending notification messages to
* customers/clients.
* @param notificationType Type of notification to be sent.
* @param customerId Id of the receipient/customer.
* @param data Additional data to be added to the message if any.
*/
fun notify(notificationType: NotificationType, customerId: String, data: Map<String, Any> = emptyMap())

/**
* Low level interface for sending notification messages to
* customers/clients.
* @param customerId Id of the customer to receive the notification.
* @param title The 'title' part of the notification.
* @param body The message part of the notification.
* @param data Additional data to be added to the message if any.
*/
fun notify(customerId: String, title: String, body: String, data: Map<String, Any> = emptyMap())
}

0 comments on commit ed599a4

Please sign in to comment.