Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions OneSignalSDK/detekt/detekt-baseline-notifications.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
<ID>ConstructorParameterNaming:NotificationLifecycleService.kt$NotificationLifecycleService$private val _backend: INotificationBackendService</ID>
<ID>ConstructorParameterNaming:NotificationLifecycleService.kt$NotificationLifecycleService$private val _configModelStore: ConfigModelStore</ID>
<ID>ConstructorParameterNaming:NotificationLifecycleService.kt$NotificationLifecycleService$private val _deviceService: IDeviceService</ID>
<ID>ConstructorParameterNaming:NotificationLifecycleService.kt$NotificationLifecycleService$private val _identityModelStore: IdentityModelStore</ID>
<ID>ConstructorParameterNaming:NotificationLifecycleService.kt$NotificationLifecycleService$private val _influenceManager: IInfluenceManager</ID>
<ID>ConstructorParameterNaming:NotificationLifecycleService.kt$NotificationLifecycleService$private val _receiveReceiptWorkManager: IReceiveReceiptWorkManager</ID>
<ID>ConstructorParameterNaming:NotificationLifecycleService.kt$NotificationLifecycleService$private val _subscriptionManager: ISubscriptionManager</ID>
Expand Down Expand Up @@ -99,6 +100,7 @@
<ID>ConstructorParameterNaming:ReceiveReceiptProcessor.kt$ReceiveReceiptProcessor$private val _deviceService: IDeviceService</ID>
<ID>ConstructorParameterNaming:ReceiveReceiptWorkManager.kt$ReceiveReceiptWorkManager$private val _applicationService: IApplicationService</ID>
<ID>ConstructorParameterNaming:ReceiveReceiptWorkManager.kt$ReceiveReceiptWorkManager$private val _configModelStore: ConfigModelStore</ID>
<ID>ConstructorParameterNaming:ReceiveReceiptWorkManager.kt$ReceiveReceiptWorkManager$private val _identityModelStore: IdentityModelStore</ID>
<ID>ConstructorParameterNaming:ReceiveReceiptWorkManager.kt$ReceiveReceiptWorkManager$private val _subscriptionManager: ISubscriptionManager</ID>
<ID>ConstructorParameterNaming:SummaryNotificationDisplayer.kt$SummaryNotificationDisplayer$private val _applicationService: IApplicationService</ID>
<ID>ConstructorParameterNaming:SummaryNotificationDisplayer.kt$SummaryNotificationDisplayer$private val _dataController: INotificationRepository</ID>
Expand Down Expand Up @@ -136,8 +138,8 @@
<ID>LongMethod:SummaryNotificationDisplayer.kt$SummaryNotificationDisplayer$override suspend fun createSummaryNotification( notificationJob: NotificationGenerationJob, notifBuilder: NotificationDisplayBuilder.OneSignalNotificationBuilder?, groupAlertBehavior: Int, )</ID>
<ID>LongParameterList:INotificationGenerationWorkManager.kt$INotificationGenerationWorkManager$( context: Context, osNotificationId: String, androidNotificationId: Int, jsonPayload: JSONObject?, timestamp: Long, restoreReason: NotificationRestoreReason?, isHighPriority: Boolean, )</ID>
<ID>LongParameterList:INotificationRepository.kt$INotificationRepository$( id: String, groupId: String?, collapseKey: String?, shouldDismissIdenticals: Boolean, isOpened: Boolean, androidId: Int, title: String?, body: String?, expireTime: Long, jsonPayload: String, )</ID>
<ID>LongParameterList:NotificationLifecycleService.kt$NotificationLifecycleService$( private val _applicationService: IApplicationService, private val _time: ITime, private val _configModelStore: ConfigModelStore, private val _influenceManager: IInfluenceManager, private val _subscriptionManager: ISubscriptionManager, private val _deviceService: IDeviceService, private val _backend: INotificationBackendService, private val _receiveReceiptWorkManager: IReceiveReceiptWorkManager, private val _analyticsTracker: IAnalyticsTracker, )</ID>
<ID>LoopWithTooManyJumpStatements:NotificationLifecycleService.kt$NotificationLifecycleService$for (i in 0 until data.length()) { val notificationId = NotificationFormatHelper.getOSNotificationIdFromJson(data[i] as JSONObject?) ?: continue if (postedOpenedNotifIds.contains(notificationId)) { continue } postedOpenedNotifIds.add(notificationId) suspendifyWithErrorHandling( useIO = true, // or false for CPU operations block = { confirmNotificationOpened(appId, notificationId, subscriptionId, deviceType) }, onError = { ex -> if (ex is BackendException) { Logging.info("Notification opened confirmation failed with statusCode: ${ex.statusCode} response: ${ex.response}") } else { Logging.info("Unexpected error in notification opened confirmation", ex) } }, ) }</ID>
<ID>LongParameterList:NotificationLifecycleService.kt$NotificationLifecycleService$( private val _applicationService: IApplicationService, private val _time: ITime, private val _configModelStore: ConfigModelStore, private val _influenceManager: IInfluenceManager, private val _subscriptionManager: ISubscriptionManager, private val _identityModelStore: IdentityModelStore, private val _deviceService: IDeviceService, private val _backend: INotificationBackendService, private val _receiveReceiptWorkManager: IReceiveReceiptWorkManager, private val _analyticsTracker: IAnalyticsTracker, )</ID>
<ID>LoopWithTooManyJumpStatements:NotificationLifecycleService.kt$NotificationLifecycleService$for (i in 0 until data.length()) { val notificationId = NotificationFormatHelper.getOSNotificationIdFromJson(data[i] as JSONObject?) ?: continue if (postedOpenedNotifIds.contains(notificationId)) { continue } postedOpenedNotifIds.add(notificationId) suspendifyWithErrorHandling( useIO = true, // or false for CPU operations block = { confirmNotificationOpened(appId, notificationId, subscriptionId, deviceType, onesignalId) }, onError = { ex -> if (ex is BackendException) { Logging.info("Notification opened confirmation failed with statusCode: ${ex.statusCode} response: ${ex.response}") } else { Logging.info("Unexpected error in notification opened confirmation", ex) } }, ) }</ID>
<ID>MagicNumber:FirebaseAnalyticsTracker.kt$FirebaseAnalyticsTracker$1000</ID>
<ID>MagicNumber:FirebaseAnalyticsTracker.kt$FirebaseAnalyticsTracker$30</ID>
<ID>MagicNumber:FirebaseAnalyticsTracker.kt$FirebaseAnalyticsTracker$60</ID>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.onesignal.user.internal.identity

import com.onesignal.common.IDManager
import com.onesignal.common.modeling.SimpleModelStore
import com.onesignal.common.modeling.SingletonModelStore
import com.onesignal.core.internal.preferences.IPreferencesService
Expand All @@ -16,3 +17,13 @@ open class IdentityModelStore(prefs: IPreferencesService) : SingletonModelStore<
* Used to determine if a user is already initialized or needs to be created.
*/
fun IdentityModelStore.hasOneSignalId(): Boolean = model.hasProperty(IdentityConstants.ONESIGNAL_ID)

/**
* The OneSignal ID the backend assigned to the current user, or null while the user only
* has a local placeholder ID (before the create user response) or no ID at all.
*/
fun IdentityModelStore.backendOneSignalIdOrNull(): String? {
if (!hasOneSignalId()) return null
val onesignalId = model.onesignalId
return if (onesignalId.isEmpty() || IDManager.isLocalId(onesignalId)) null else onesignalId
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.onesignal.user.internal.identity

import com.onesignal.common.IDManager
import com.onesignal.mocks.MockHelper
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.nulls.shouldBeNull
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockk

class IdentityModelStoreTests : FunSpec({
test("backendOneSignalIdOrNull returns the id once the backend has assigned one") {
// Given
val store = MockHelper.identityModelStore { it.onesignalId = "onesignalId" }

// When / Then
store.backendOneSignalIdOrNull() shouldBe "onesignalId"
}

test("backendOneSignalIdOrNull is null while the user only has a local placeholder id") {
// Given: first launch, login() and logout() all assign a local id that the create user
// response later replaces. Requests that name the user by onesignal_id must not send it,
// since the server would look up a user it has never seen.
val store = MockHelper.identityModelStore { it.onesignalId = IDManager.createLocalId() }

// When / Then
store.backendOneSignalIdOrNull().shouldBeNull()
}

test("backendOneSignalIdOrNull is null when the id is empty") {
// Given
val store = MockHelper.identityModelStore { it.onesignalId = "" }

// When / Then
store.backendOneSignalIdOrNull().shouldBeNull()
}

test("backendOneSignalIdOrNull is null when the identity model has no id at all") {
// Given: the singleton store hands out an empty model before the first user is created,
// and reading onesignalId on it throws, so the helper has to check for the property first.
val store = mockk<IdentityModelStore>(relaxed = true)
every { store.model } returns IdentityModel()

// When / Then
store.backendOneSignalIdOrNull().shouldBeNull()
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ internal interface INotificationBackendService {
* @param notificationId The ID of the notification within the [appId] that has been received.
* @param subscriptionId The specific subscription within the [appId] the notification has been received for.
* @param deviceType The type of device the notification was received at.
* @param onesignalId The backend-assigned OneSignal ID of the user the subscription belonged to
* when the notification was received, or null when the SDK did not have one. Omitted from the
* request when null.
*/
suspend fun updateNotificationAsReceived(
appId: String,
notificationId: String,
subscriptionId: String,
deviceType: IDeviceService.DeviceType,
onesignalId: String?,
)

/**
Expand All @@ -33,11 +37,14 @@ internal interface INotificationBackendService {
* @param notificationId The ID of the notification within the [appId] that has been received.
* @param subscriptionId The specific subscription within the [appId] the notification has been received for.
* @param deviceType The type of device the notification was received at.
* @param onesignalId The backend-assigned OneSignal ID of the user the subscription belongs to,
* or null when the SDK does not have one. Omitted from the request when null.
*/
suspend fun updateNotificationAsOpened(
appId: String,
notificationId: String,
subscriptionId: String,
deviceType: IDeviceService.DeviceType,
onesignalId: String?,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@ internal class NotificationBackendService(
notificationId: String,
subscriptionId: String,
deviceType: IDeviceService.DeviceType,
onesignalId: String?,
) {
val jsonBody: JSONObject =
JSONObject()
.put("app_id", appId)
.put("player_id", subscriptionId)
.put("device_type", deviceType.value)

if (onesignalId != null) {
jsonBody.put("onesignal_id", onesignalId)
}

var response = _httpClient.put("notifications/$notificationId/report_received", jsonBody)

if (!response.isSuccess) {
Expand All @@ -33,12 +38,16 @@ internal class NotificationBackendService(
notificationId: String,
subscriptionId: String,
deviceType: IDeviceService.DeviceType,
onesignalId: String?,
) {
val jsonBody = JSONObject()
jsonBody.put("app_id", appId)
jsonBody.put("player_id", subscriptionId)
jsonBody.put("opened", true)
jsonBody.put("device_type", deviceType.value)
if (onesignalId != null) {
jsonBody.put("onesignal_id", onesignalId)
}

var response = _httpClient.put("notifications/$notificationId", jsonBody)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import com.onesignal.notifications.internal.lifecycle.INotificationLifecycleCall
import com.onesignal.notifications.internal.lifecycle.INotificationLifecycleService
import com.onesignal.notifications.internal.receivereceipt.IReceiveReceiptWorkManager
import com.onesignal.session.internal.influence.IInfluenceManager
import com.onesignal.user.internal.identity.IdentityModelStore
import com.onesignal.user.internal.identity.backendOneSignalIdOrNull
import com.onesignal.user.internal.subscriptions.ISubscriptionManager
import kotlinx.coroutines.delay
import org.json.JSONArray
Expand All @@ -47,6 +49,7 @@ internal class NotificationLifecycleService(
private val _configModelStore: ConfigModelStore,
private val _influenceManager: IInfluenceManager,
private val _subscriptionManager: ISubscriptionManager,
private val _identityModelStore: IdentityModelStore,
private val _deviceService: IDeviceService,
private val _backend: INotificationBackendService,
private val _receiveReceiptWorkManager: IReceiveReceiptWorkManager,
Expand Down Expand Up @@ -135,6 +138,7 @@ internal class NotificationLifecycleService(
val config = _configModelStore.model
val appId: String = config.appId ?: ""
val subscriptionId: String = _subscriptionManager.subscriptions.push.id
val onesignalId = _identityModelStore.backendOneSignalIdOrNull()
val deviceType = _deviceService.deviceType

for (i in 0 until data.length()) {
Expand All @@ -150,7 +154,7 @@ internal class NotificationLifecycleService(
useIO = true,
// or false for CPU operations
block = {
confirmNotificationOpened(appId, notificationId, subscriptionId, deviceType)
confirmNotificationOpened(appId, notificationId, subscriptionId, deviceType, onesignalId)
},
onError = { ex ->
if (ex is BackendException) {
Expand Down Expand Up @@ -204,10 +208,11 @@ internal class NotificationLifecycleService(
notificationId: String,
subscriptionId: String,
deviceType: IDeviceService.DeviceType,
onesignalId: String?,
) {
for (attempt in 1..NetworkUtils.maxNetworkRequestAttemptCount) {
try {
_backend.updateNotificationAsOpened(appId, notificationId, subscriptionId, deviceType)
_backend.updateNotificationAsOpened(appId, notificationId, subscriptionId, deviceType, onesignalId)
return
} catch (ex: BackendException) {
val responseType = NetworkUtils.getResponseStatusType(ex.statusCode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ internal interface IReceiveReceiptProcessor {
* @param appId The id of the application the notification was received under.
* @param subscriptionId The id of the subscription the notification was received under.
* @param notificationId The id of the notification that has been received.
* @param onesignalId The backend-assigned OneSignal id of the user the subscription belonged to
* when the notification was received, or null when the SDK did not have one.
*/
suspend fun sendReceiveReceipt(
appId: String,
subscriptionId: String,
notificationId: String,
onesignalId: String?,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ internal class ReceiveReceiptProcessor(
appId: String,
subscriptionId: String,
notificationId: String,
onesignalId: String?,
) {
val deviceType = _deviceService.deviceType

try {
_backend.updateNotificationAsReceived(appId, notificationId, subscriptionId, deviceType)
_backend.updateNotificationAsReceived(appId, notificationId, subscriptionId, deviceType, onesignalId)
} catch (ex: BackendException) {
Logging.info("Receive receipt failed with statusCode: ${ex.statusCode} response: ${ex.response}")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ import com.onesignal.debug.internal.logging.Logging
import com.onesignal.notifications.internal.common.OSWorkManagerHelper
import com.onesignal.notifications.internal.receivereceipt.IReceiveReceiptProcessor
import com.onesignal.notifications.internal.receivereceipt.IReceiveReceiptWorkManager
import com.onesignal.user.internal.identity.IdentityModelStore
import com.onesignal.user.internal.identity.backendOneSignalIdOrNull
import com.onesignal.user.internal.subscriptions.ISubscriptionManager
import java.util.concurrent.TimeUnit

internal class ReceiveReceiptWorkManager(
private val _applicationService: IApplicationService,
private val _configModelStore: ConfigModelStore,
private val _subscriptionManager: ISubscriptionManager,
private val _identityModelStore: IdentityModelStore,
) : IReceiveReceiptWorkManager {
private val minDelay = 0
private val maxDelay = 25
Expand All @@ -35,6 +38,10 @@ internal class ReceiveReceiptWorkManager(

val appId: String = _configModelStore.model.appId
val subscriptionId = _subscriptionManager.subscriptions.push.id
// Captured now, with the subscription, so the receipt names the user who held the
// subscription when the notification arrived rather than whoever is logged in when the
// delayed worker runs.
val onesignalId = _identityModelStore.backendOneSignalIdOrNull()

if (subscriptionId.isEmpty() || appId.isEmpty()) {
Logging.debug("ReceiveReceiptWorkManager: No push subscription or appId!")
Expand All @@ -46,6 +53,7 @@ internal class ReceiveReceiptWorkManager(
.putString(OS_NOTIFICATION_ID, notificationId)
.putString(OS_APP_ID, appId)
.putString(OS_SUBSCRIPTION_ID, subscriptionId)
.putString(OS_ONESIGNAL_ID, onesignalId)
.build()
val constraints = buildConstraints()
val workRequest =
Expand Down Expand Up @@ -81,9 +89,10 @@ internal class ReceiveReceiptWorkManager(
val notificationId = inputData.getString(OS_NOTIFICATION_ID) ?: return Result.failure()
val appId = inputData.getString(OS_APP_ID) ?: return Result.failure()
val subscriptionId = inputData.getString(OS_SUBSCRIPTION_ID) ?: return Result.failure()
val onesignalId = inputData.getString(OS_ONESIGNAL_ID)

val receiveReceiptProcessor = OneSignal.getService<IReceiveReceiptProcessor>()
receiveReceiptProcessor.sendReceiveReceipt(appId, subscriptionId, notificationId)
receiveReceiptProcessor.sendReceiveReceipt(appId, subscriptionId, notificationId, onesignalId)
return Result.success()
}
}
Expand All @@ -92,5 +101,6 @@ internal class ReceiveReceiptWorkManager(
private const val OS_NOTIFICATION_ID = "os_notification_id"
private const val OS_APP_ID = "os_app_id"
private const val OS_SUBSCRIPTION_ID = "os_subscription_id"
private const val OS_ONESIGNAL_ID = "os_onesignal_id"
}
}
Loading
Loading