diff --git a/OneSignalSDK/detekt/detekt-baseline-core.xml b/OneSignalSDK/detekt/detekt-baseline-core.xml
index 0530b2d0eb..b5361b0bb7 100644
--- a/OneSignalSDK/detekt/detekt-baseline-core.xml
+++ b/OneSignalSDK/detekt/detekt-baseline-core.xml
@@ -22,6 +22,7 @@
ConstructorParameterNaming:ConfigModelStoreListener.kt$ConfigModelStoreListener$private val _configModelStore: ConfigModelStore
ConstructorParameterNaming:ConfigModelStoreListener.kt$ConfigModelStoreListener$private val _paramsBackendService: IParamsBackendService
ConstructorParameterNaming:ConfigModelStoreListener.kt$ConfigModelStoreListener$private val _subscriptionManager: ISubscriptionManager
+ ConstructorParameterNaming:CustomEventOperationExecutor.kt$CustomEventOperationExecutor$private val _jwtTokenStore: JwtTokenStore
ConstructorParameterNaming:DatabaseCursor.kt$DatabaseCursor$private val _cursor: Cursor
ConstructorParameterNaming:DatabaseProvider.kt$DatabaseProvider$private val _application: IApplicationService
ConstructorParameterNaming:DeviceService.kt$DeviceService$private val _applicationService: IApplicationService
@@ -33,16 +34,26 @@
ConstructorParameterNaming:HttpConnectionFactory.kt$HttpConnectionFactory$private val _configModelStore: ConfigModelStore
ConstructorParameterNaming:IdentityBackendService.kt$IdentityBackendService$private val _httpClient: IHttpClient
ConstructorParameterNaming:IdentityModelStoreListener.kt$IdentityModelStoreListener$private val _configModelStore: ConfigModelStore
+ ConstructorParameterNaming:IdentityModelStoreListener.kt$IdentityModelStoreListener$private val _identityModelStore: IdentityModelStore
ConstructorParameterNaming:IdentityOperationExecutor.kt$IdentityOperationExecutor$private val _buildUserService: IRebuildUserService
+ ConstructorParameterNaming:IdentityOperationExecutor.kt$IdentityOperationExecutor$private val _configModelStore: ConfigModelStore
ConstructorParameterNaming:IdentityOperationExecutor.kt$IdentityOperationExecutor$private val _identityBackend: IIdentityBackendService
ConstructorParameterNaming:IdentityOperationExecutor.kt$IdentityOperationExecutor$private val _identityModelStore: IdentityModelStore
+ ConstructorParameterNaming:IdentityOperationExecutor.kt$IdentityOperationExecutor$private val _jwtTokenStore: JwtTokenStore
ConstructorParameterNaming:IdentityOperationExecutor.kt$IdentityOperationExecutor$private val _newRecordState: NewRecordsState
+ ConstructorParameterNaming:IdentityVerificationService.kt$IdentityVerificationService$private val _configModelStore: ConfigModelStore
+ ConstructorParameterNaming:IdentityVerificationService.kt$IdentityVerificationService$private val _identityModelStore: IdentityModelStore
+ ConstructorParameterNaming:IdentityVerificationService.kt$IdentityVerificationService$private val _jwtTokenStore: JwtTokenStore
+ ConstructorParameterNaming:IdentityVerificationService.kt$IdentityVerificationService$private val _operationRepo: IOperationRepo
+ ConstructorParameterNaming:IdentityVerificationService.kt$IdentityVerificationService$private val _userManager: UserManager
ConstructorParameterNaming:InfluenceDataRepository.kt$InfluenceDataRepository$private val _configModelStore: ConfigModelStore
ConstructorParameterNaming:InfluenceManager.kt$InfluenceManager$private val _applicationService: IApplicationService
ConstructorParameterNaming:InfluenceManager.kt$InfluenceManager$private val _configModelStore: ConfigModelStore
ConstructorParameterNaming:InfluenceManager.kt$InfluenceManager$private val _sessionService: ISessionService
ConstructorParameterNaming:InstallIdService.kt$InstallIdService$private val _prefs: IPreferencesService
+ ConstructorParameterNaming:JwtTokenStore.kt$JwtTokenStore$private val _prefs: IPreferencesService
ConstructorParameterNaming:LanguageContext.kt$LanguageContext$private val _propertiesModelStore: PropertiesModelStore
+ ConstructorParameterNaming:LoginUserFromSubscriptionOperationExecutor.kt$LoginUserFromSubscriptionOperationExecutor$private val _configModelStore: ConfigModelStore
ConstructorParameterNaming:LoginUserFromSubscriptionOperationExecutor.kt$LoginUserFromSubscriptionOperationExecutor$private val _identityModelStore: IdentityModelStore
ConstructorParameterNaming:LoginUserFromSubscriptionOperationExecutor.kt$LoginUserFromSubscriptionOperationExecutor$private val _propertiesModelStore: PropertiesModelStore
ConstructorParameterNaming:LoginUserFromSubscriptionOperationExecutor.kt$LoginUserFromSubscriptionOperationExecutor$private val _subscriptionBackend: ISubscriptionBackendService
@@ -51,6 +62,7 @@
ConstructorParameterNaming:LoginUserOperationExecutor.kt$LoginUserOperationExecutor$private val _deviceService: IDeviceService
ConstructorParameterNaming:LoginUserOperationExecutor.kt$LoginUserOperationExecutor$private val _identityModelStore: IdentityModelStore
ConstructorParameterNaming:LoginUserOperationExecutor.kt$LoginUserOperationExecutor$private val _identityOperationExecutor: IdentityOperationExecutor
+ ConstructorParameterNaming:LoginUserOperationExecutor.kt$LoginUserOperationExecutor$private val _jwtTokenStore: JwtTokenStore
ConstructorParameterNaming:LoginUserOperationExecutor.kt$LoginUserOperationExecutor$private val _languageContext: ILanguageContext
ConstructorParameterNaming:LoginUserOperationExecutor.kt$LoginUserOperationExecutor$private val _propertiesModelStore: PropertiesModelStore
ConstructorParameterNaming:LoginUserOperationExecutor.kt$LoginUserOperationExecutor$private val _subscriptionsModelStore: SubscriptionModelStore
@@ -62,6 +74,8 @@
ConstructorParameterNaming:NewRecordsState.kt$NewRecordsState$private val _time: ITime
ConstructorParameterNaming:OSDatabase.kt$OSDatabase$private val _outcomeTableProvider: OutcomeTableProvider
ConstructorParameterNaming:OperationRepo.kt$OperationRepo$private val _configModelStore: ConfigModelStore
+ ConstructorParameterNaming:OperationRepo.kt$OperationRepo$private val _identityModelStore: IdentityModelStore
+ ConstructorParameterNaming:OperationRepo.kt$OperationRepo$private val _jwtTokenStore: JwtTokenStore
ConstructorParameterNaming:OperationRepo.kt$OperationRepo$private val _newRecordState: NewRecordsState
ConstructorParameterNaming:OperationRepo.kt$OperationRepo$private val _operationModelStore: OperationModelStore
ConstructorParameterNaming:OperationRepo.kt$OperationRepo$private val _time: ITime
@@ -81,6 +95,7 @@
ConstructorParameterNaming:PreferencesService.kt$PreferencesService$private val _applicationService: IApplicationService
ConstructorParameterNaming:PreferencesService.kt$PreferencesService$private val _time: ITime
ConstructorParameterNaming:PropertiesModelStoreListener.kt$PropertiesModelStoreListener$private val _configModelStore: ConfigModelStore
+ ConstructorParameterNaming:PropertiesModelStoreListener.kt$PropertiesModelStoreListener$private val _identityModelStore: IdentityModelStore
ConstructorParameterNaming:RebuildUserService.kt$RebuildUserService$private val _configModelStore: ConfigModelStore
ConstructorParameterNaming:RebuildUserService.kt$RebuildUserService$private val _identityModelStore: IdentityModelStore
ConstructorParameterNaming:RebuildUserService.kt$RebuildUserService$private val _propertiesModelStore: PropertiesModelStore
@@ -93,6 +108,7 @@
ConstructorParameterNaming:RefreshUserOperationExecutor.kt$RefreshUserOperationExecutor$private val _buildUserService: IRebuildUserService
ConstructorParameterNaming:RefreshUserOperationExecutor.kt$RefreshUserOperationExecutor$private val _configModelStore: ConfigModelStore
ConstructorParameterNaming:RefreshUserOperationExecutor.kt$RefreshUserOperationExecutor$private val _identityModelStore: IdentityModelStore
+ ConstructorParameterNaming:RefreshUserOperationExecutor.kt$RefreshUserOperationExecutor$private val _jwtTokenStore: JwtTokenStore
ConstructorParameterNaming:RefreshUserOperationExecutor.kt$RefreshUserOperationExecutor$private val _newRecordState: NewRecordsState
ConstructorParameterNaming:RefreshUserOperationExecutor.kt$RefreshUserOperationExecutor$private val _propertiesModelStore: PropertiesModelStore
ConstructorParameterNaming:RefreshUserOperationExecutor.kt$RefreshUserOperationExecutor$private val _subscriptionsModelStore: SubscriptionModelStore
@@ -123,6 +139,7 @@
ConstructorParameterNaming:SubscriptionOperationExecutor.kt$SubscriptionOperationExecutor$private val _configModelStore: ConfigModelStore
ConstructorParameterNaming:SubscriptionOperationExecutor.kt$SubscriptionOperationExecutor$private val _consistencyManager: IConsistencyManager
ConstructorParameterNaming:SubscriptionOperationExecutor.kt$SubscriptionOperationExecutor$private val _deviceService: IDeviceService
+ ConstructorParameterNaming:SubscriptionOperationExecutor.kt$SubscriptionOperationExecutor$private val _jwtTokenStore: JwtTokenStore
ConstructorParameterNaming:SubscriptionOperationExecutor.kt$SubscriptionOperationExecutor$private val _newRecordState: NewRecordsState
ConstructorParameterNaming:SubscriptionOperationExecutor.kt$SubscriptionOperationExecutor$private val _subscriptionBackend: ISubscriptionBackendService
ConstructorParameterNaming:SubscriptionOperationExecutor.kt$SubscriptionOperationExecutor$private val _subscriptionModelStore: SubscriptionModelStore
@@ -132,8 +149,10 @@
ConstructorParameterNaming:TrackGooglePurchase.kt$TrackGooglePurchase$private val _operationRepo: IOperationRepo
ConstructorParameterNaming:TrackGooglePurchase.kt$TrackGooglePurchase$private val _prefs: IPreferencesService
ConstructorParameterNaming:UpdateUserOperationExecutor.kt$UpdateUserOperationExecutor$private val _buildUserService: IRebuildUserService
+ ConstructorParameterNaming:UpdateUserOperationExecutor.kt$UpdateUserOperationExecutor$private val _configModelStore: ConfigModelStore
ConstructorParameterNaming:UpdateUserOperationExecutor.kt$UpdateUserOperationExecutor$private val _consistencyManager: IConsistencyManager
ConstructorParameterNaming:UpdateUserOperationExecutor.kt$UpdateUserOperationExecutor$private val _identityModelStore: IdentityModelStore
+ ConstructorParameterNaming:UpdateUserOperationExecutor.kt$UpdateUserOperationExecutor$private val _jwtTokenStore: JwtTokenStore
ConstructorParameterNaming:UpdateUserOperationExecutor.kt$UpdateUserOperationExecutor$private val _newRecordState: NewRecordsState
ConstructorParameterNaming:UpdateUserOperationExecutor.kt$UpdateUserOperationExecutor$private val _propertiesModelStore: PropertiesModelStore
ConstructorParameterNaming:UpdateUserOperationExecutor.kt$UpdateUserOperationExecutor$private val _userBackend: IUserBackendService
@@ -158,10 +177,6 @@
ForbiddenComment:HttpClient.kt$HttpClient$// TODO: SHOULD RETURN OK INSTEAD OF NOT_MODIFIED TO MAKE TRANSPARENT?
ForbiddenComment:IPreferencesService.kt$PreferenceOneSignalKeys$* (String) The serialized IAMs TODO: This isn't currently used, determine if actually needed for cold start IAM fetch delay
ForbiddenComment:IUserBackendService.kt$IUserBackendService$// TODO: Change to send only the push subscription, optimally
- ForbiddenComment:LoginHelper.kt$LoginHelper$// TODO: Set JWT Token for all future requests.
- ForbiddenComment:LogoutHelper.kt$LogoutHelper$// TODO: remove JWT Token for all future requests.
- ForbiddenComment:OperationRepo.kt$OperationRepo$// TODO: Need to provide callback for app to reset JWT. For now, fail with no retry.
- ForbiddenComment:ParamsBackendService.kt$ParamsBackendService$// TODO: New
ForbiddenComment:PermissionsActivity.kt$PermissionsActivity$// TODO after we remove IAM from being an activity window we may be able to remove this handler
ForbiddenComment:PermissionsActivity.kt$PermissionsActivity$// TODO improve this method
ForbiddenComment:PermissionsViewModel.kt$PermissionsViewModel.Companion$// TODO this will be removed once the handler is deleted
@@ -192,15 +207,16 @@
LongMethod:TrackGooglePurchase.kt$TrackGooglePurchase$private fun queryBoughtItems()
LongMethod:TrackGooglePurchase.kt$TrackGooglePurchase$private fun sendPurchases( skusToAdd: ArrayList<String>, newPurchaseTokens: ArrayList<String>, )
LongMethod:UpdateUserOperationExecutor.kt$UpdateUserOperationExecutor$override suspend fun execute(operations: List<Operation>): ExecutionResponse
- LongParameterList:ICustomEventBackendService.kt$ICustomEventBackendService$( appId: String, onesignalId: String, externalId: String?, timestamp: Long, eventName: String, eventProperties: String?, metadata: CustomEventMetadata, )
+ LongParameterList:ICustomEventBackendService.kt$ICustomEventBackendService$( appId: String, onesignalId: String, externalId: String?, timestamp: Long, eventName: String, eventProperties: String?, metadata: CustomEventMetadata, jwt: String? = null, )
LongParameterList:IDatabase.kt$IDatabase$( table: String, columns: Array<String>? = null, whereClause: String? = null, whereArgs: Array<String>? = null, groupBy: String? = null, having: String? = null, orderBy: String? = null, limit: String? = null, action: (ICursor) -> Unit, )
LongParameterList:IOutcomeEventsBackendService.kt$IOutcomeEventsBackendService$( appId: String, userId: String, subscriptionId: String, deviceType: String, direct: Boolean?, event: OutcomeEvent, )
- LongParameterList:IParamsBackendService.kt$ParamsObject$( var googleProjectNumber: String? = null, var enterprise: Boolean? = null, var useIdentityVerification: Boolean? = null, var notificationChannels: JSONArray? = null, var firebaseAnalytics: Boolean? = null, var restoreTTLFilter: Boolean? = null, var clearGroupOnSummaryClick: Boolean? = null, var receiveReceiptEnabled: Boolean? = null, var disableGMSMissingPrompt: Boolean? = null, var unsubscribeWhenNotificationsDisabled: Boolean? = null, var locationShared: Boolean? = null, var requiresUserPrivacyConsent: Boolean? = null, var opRepoExecutionInterval: Long? = null, var influenceParams: InfluenceParamsObject, var fcmParams: FCMParamsObject, )
- LongParameterList:IUserBackendService.kt$IUserBackendService$( appId: String, aliasLabel: String, aliasValue: String, properties: PropertiesObject, refreshDeviceMetadata: Boolean, propertyiesDelta: PropertiesDeltasObject, )
- LongParameterList:LoginUserOperationExecutor.kt$LoginUserOperationExecutor$( private val _identityOperationExecutor: IdentityOperationExecutor, private val _application: IApplicationService, private val _deviceService: IDeviceService, private val _userBackend: IUserBackendService, private val _identityModelStore: IdentityModelStore, private val _propertiesModelStore: PropertiesModelStore, private val _subscriptionsModelStore: SubscriptionModelStore, private val _configModelStore: ConfigModelStore, private val _languageContext: ILanguageContext, )
+ LongParameterList:IUserBackendService.kt$IUserBackendService$( appId: String, aliasLabel: String, aliasValue: String, properties: PropertiesObject, refreshDeviceMetadata: Boolean, propertyiesDelta: PropertiesDeltasObject, jwt: String? = null, )
+ LongParameterList:LoginUserOperationExecutor.kt$LoginUserOperationExecutor$( private val _identityOperationExecutor: IdentityOperationExecutor, private val _application: IApplicationService, private val _deviceService: IDeviceService, private val _userBackend: IUserBackendService, private val _identityModelStore: IdentityModelStore, private val _propertiesModelStore: PropertiesModelStore, private val _subscriptionsModelStore: SubscriptionModelStore, private val _configModelStore: ConfigModelStore, private val _languageContext: ILanguageContext, private val _jwtTokenStore: JwtTokenStore, )
LongParameterList:OutcomeEventsController.kt$OutcomeEventsController$( private val _session: ISessionService, private val _influenceManager: IInfluenceManager, private val _outcomeEventsCache: IOutcomeEventsRepository, private val _outcomeEventsPreferences: IOutcomeEventsPreferences, private val _outcomeEventsBackend: IOutcomeEventsBackendService, private val _configModelStore: ConfigModelStore, private val _identityModelStore: IdentityModelStore, private val _subscriptionManager: ISubscriptionManager, private val _deviceService: IDeviceService, private val _time: ITime, )
+ LongParameterList:RefreshUserOperationExecutor.kt$RefreshUserOperationExecutor$( private val _userBackend: IUserBackendService, private val _identityModelStore: IdentityModelStore, private val _propertiesModelStore: PropertiesModelStore, private val _subscriptionsModelStore: SubscriptionModelStore, private val _configModelStore: ConfigModelStore, private val _buildUserService: IRebuildUserService, private val _newRecordState: NewRecordsState, private val _jwtTokenStore: JwtTokenStore, )
LongParameterList:SubscriptionObject.kt$SubscriptionObject$( val id: String? = null, val type: SubscriptionObjectType? = null, val token: String? = null, val enabled: Boolean? = null, val notificationTypes: Int? = null, val sdk: String? = null, val deviceModel: String? = null, val deviceOS: String? = null, val rooted: Boolean? = null, val netType: Int? = null, val carrier: String? = null, val appVersion: String? = null, )
- LongParameterList:SubscriptionOperationExecutor.kt$SubscriptionOperationExecutor$( private val _subscriptionBackend: ISubscriptionBackendService, private val _deviceService: IDeviceService, private val _applicationService: IApplicationService, private val _subscriptionModelStore: SubscriptionModelStore, private val _configModelStore: ConfigModelStore, private val _buildUserService: IRebuildUserService, private val _newRecordState: NewRecordsState, private val _consistencyManager: IConsistencyManager, )
+ LongParameterList:SubscriptionOperationExecutor.kt$SubscriptionOperationExecutor$( private val _subscriptionBackend: ISubscriptionBackendService, private val _deviceService: IDeviceService, private val _applicationService: IApplicationService, private val _subscriptionModelStore: SubscriptionModelStore, private val _configModelStore: ConfigModelStore, private val _buildUserService: IRebuildUserService, private val _newRecordState: NewRecordsState, private val _consistencyManager: IConsistencyManager, private val _jwtTokenStore: JwtTokenStore, )
+ LongParameterList:UpdateUserOperationExecutor.kt$UpdateUserOperationExecutor$( private val _userBackend: IUserBackendService, private val _identityModelStore: IdentityModelStore, private val _propertiesModelStore: PropertiesModelStore, private val _buildUserService: IRebuildUserService, private val _newRecordState: NewRecordsState, private val _consistencyManager: IConsistencyManager, private val _configModelStore: ConfigModelStore, private val _jwtTokenStore: JwtTokenStore, )
LongParameterList:UserSwitcher.kt$UserSwitcher$( private val preferencesService: IPreferencesService, private val operationRepo: IOperationRepo, private val services: ServiceProvider, private val idManager: IDManager = IDManager, private val identityModelStore: IdentityModelStore, private val propertiesModelStore: PropertiesModelStore, private val subscriptionModelStore: SubscriptionModelStore, private val configModel: ConfigModel, private val oneSignalUtils: OneSignalUtils = OneSignalUtils, private val carrierName: String? = null, private val deviceOS: String? = null, private val androidUtils: AndroidUtils = AndroidUtils, private val appContextProvider: () -> Context, )
LoopWithTooManyJumpStatements:ModelStore.kt$ModelStore$for (index in jsonArray.length() - 1 downTo 0) { val newModel = create(jsonArray.getJSONObject(index)) ?: continue /* * NOTE: Migration fix for bug introduced in 5.1.12 * The following check is intended for the operation model store. * When the call to this method moved out of the operation model store's initializer, * duplicate operations could be cached. * See https://github.com/OneSignal/OneSignal-Android-SDK/pull/2099 */ val hasExisting = models.any { it.id == newModel.id } if (hasExisting) { Logging.debug("ModelStore<$name>: load - operation.id: ${newModel.id} already exists in the store.") continue } models.add(0, newModel) // listen for changes to this model newModel.subscribe(this) }
MagicNumber:ApplicationService.kt$ApplicationService$50
@@ -265,6 +281,7 @@
NestedBlockDepth:InfluenceManager.kt$InfluenceManager$private fun attemptSessionUpgrade( entryAction: AppEntryAction, directId: String? = null, )
NestedBlockDepth:JSONUtils.kt$JSONUtils$fun compareJSONArrays( jsonArray1: JSONArray?, jsonArray2: JSONArray?, ): Boolean
NestedBlockDepth:LoginUserOperationExecutor.kt$LoginUserOperationExecutor$private suspend fun createUser( createUserOperation: LoginUserOperation, operations: List<Operation>, ): ExecutionResponse
+ NestedBlockDepth:OperationRepo.kt$OperationRepo$internal suspend fun executeOperations(ops: List<OperationQueueItem>)
NestedBlockDepth:RefreshUserOperationExecutor.kt$RefreshUserOperationExecutor$private suspend fun getUser(op: RefreshUserOperation): ExecutionResponse
NestedBlockDepth:ServiceRegistration.kt$ServiceRegistrationReflection$override fun resolve(provider: IServiceProvider): Any?
NestedBlockDepth:ServiceRegistration.kt$ServiceRegistrationReflection$private fun doesHaveAllParameters( constructor: Constructor<*>, provider: IServiceProvider, ): Boolean
@@ -281,7 +298,6 @@
RethrowCaughtException:OSDatabase.kt$OSDatabase$throw e
ReturnCount:AppIdResolution.kt$fun resolveAppId( inputAppId: String?, configModel: ConfigModel, preferencesService: IPreferencesService, ): AppIdResolution
ReturnCount:BackgroundManager.kt$BackgroundManager$override fun cancelRunBackgroundServices(): Boolean
- ReturnCount:OneSignalImp.kt$OneSignalImp$private fun internalInit( context: Context, appId: String?, ): Boolean
ReturnCount:ConfigModel.kt$ConfigModel$override fun createModelForProperty( property: String, jsonObject: JSONObject, ): Model?
ReturnCount:HttpClient.kt$HttpClient$private suspend fun makeRequest( url: String, method: String?, jsonBody: JSONObject?, timeout: Int, headers: OptionalHeaders?, ): HttpResponse
ReturnCount:IdentityOperationExecutor.kt$IdentityOperationExecutor$override suspend fun execute(operations: List<Operation>): ExecutionResponse
@@ -294,8 +310,10 @@
ReturnCount:Model.kt$Model$protected fun getOptIntProperty( name: String, create: (() -> Int?)? = null, ): Int?
ReturnCount:Model.kt$Model$protected fun getOptLongProperty( name: String, create: (() -> Long?)? = null, ): Long?
ReturnCount:Model.kt$Model$protected inline fun <reified T : Enum<T>> getOptEnumProperty(name: String): T?
+ ReturnCount:OneSignalImp.kt$OneSignalImp$private fun internalInit( context: Context, appId: String?, ): Boolean
ReturnCount:OperationModelStore.kt$OperationModelStore$override fun create(jsonObject: JSONObject?): Operation?
ReturnCount:OperationModelStore.kt$OperationModelStore$private fun isValidOperation(jsonObject: JSONObject): Boolean
+ ReturnCount:OperationRepo.kt$OperationRepo$private fun hasValidJwtIfRequired( iv: Boolean, op: Operation, ): Boolean
ReturnCount:OutcomeEventsController.kt$OutcomeEventsController$private suspend fun sendAndCreateOutcomeEvent( name: String, weight: Float, // Note: this is optional sessionTime: Long, influences: List<Influence>, ): OutcomeEvent?
ReturnCount:OutcomeEventsController.kt$OutcomeEventsController$private suspend fun sendUniqueOutcomeEvent( name: String, sessionInfluences: List<Influence>, ): OutcomeEvent?
ReturnCount:PermissionsViewModel.kt$PermissionsViewModel$private fun shouldShowSettings( permission: String, shouldShowRationaleAfter: Boolean, ): Boolean
@@ -320,6 +338,7 @@
SwallowedException:JSONUtils.kt$JSONUtils$t: Throwable
SwallowedException:PermissionsActivity.kt$PermissionsActivity$e: ClassNotFoundException
SwallowedException:PreferencesService.kt$PreferencesService$ex: Exception
+ SwallowedException:PreferencesService.kt$PreferencesService$t: Throwable
SwallowedException:SyncJobService.kt$SyncJobService$e: Exception
SwallowedException:TrackGooglePurchase.kt$TrackGooglePurchase.Companion$t: Throwable
ThrowsCount:OneSignalImp.kt$OneSignalImp$private suspend fun waitUntilInitInternal(operationName: String? = null)
@@ -334,6 +353,7 @@
TooGenericExceptionCaught:PreferenceStoreFix.kt$PreferenceStoreFix$e: Throwable
TooGenericExceptionCaught:PreferencesService.kt$PreferencesService$e: Throwable
TooGenericExceptionCaught:PreferencesService.kt$PreferencesService$ex: Exception
+ TooGenericExceptionCaught:PreferencesService.kt$PreferencesService$t: Throwable
TooGenericExceptionCaught:SyncJobService.kt$SyncJobService$e: Exception
TooGenericExceptionCaught:ThreadUtils.kt$e: Exception
TooGenericExceptionCaught:TrackGooglePurchase.kt$TrackGooglePurchase$e: Throwable
@@ -412,10 +432,6 @@
UndocumentedPublicClass:IOperationExecutor.kt$ExecutionResponse
UndocumentedPublicClass:IOperationExecutor.kt$ExecutionResult
UndocumentedPublicClass:IOutcomeEvent.kt$IOutcomeEvent
- UndocumentedPublicClass:IParamsBackendService.kt$FCMParamsObject
- UndocumentedPublicClass:IParamsBackendService.kt$IParamsBackendService
- UndocumentedPublicClass:IParamsBackendService.kt$InfluenceParamsObject
- UndocumentedPublicClass:IParamsBackendService.kt$ParamsObject
UndocumentedPublicClass:IPreferencesService.kt$PreferenceOneSignalKeys
UndocumentedPublicClass:IPreferencesService.kt$PreferencePlayerPurchasesKeys
UndocumentedPublicClass:IPreferencesService.kt$PreferenceStores
@@ -604,11 +620,13 @@
UnusedPrivateMember:AndroidUtils.kt$AndroidUtils$var requestPermission: String? = null
UnusedPrivateMember:ApplicationService.kt$ApplicationService$val listenerKey = "decorViewReady:$runnable"
UnusedPrivateMember:JSONUtils.kt$JSONUtils$`object`: Any
- UnusedPrivateMember:LoginHelper.kt$LoginHelper$jwtBearerToken: String? = null
UnusedPrivateMember:OSDatabase.kt$OSDatabase.Companion$private const val FLOAT_TYPE = " FLOAT"
UnusedPrivateMember:OperationRepo.kt$OperationRepo$private val _time: ITime
UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("'initWithContext failed' before 'login'")
UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("'initWithContext failed' before 'logout'")
+ UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("Must call 'initWithContext' before 'login'")
+ UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("Must call 'initWithContext' before 'logout'")
+ UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("Must call 'initWithContext' before use")
UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw initFailureException ?: IllegalStateException("Initialization failed. Cannot proceed.")
diff --git a/OneSignalSDK/detekt/detekt-baseline-in-app-messages.xml b/OneSignalSDK/detekt/detekt-baseline-in-app-messages.xml
index da9439705a..fe085e6d85 100644
--- a/OneSignalSDK/detekt/detekt-baseline-in-app-messages.xml
+++ b/OneSignalSDK/detekt/detekt-baseline-in-app-messages.xml
@@ -1,9 +1,9 @@
-
+
-
+
ComplexCondition:InAppMessagesManager.kt$InAppMessagesManager$!message.isTriggerChanged && isMessageDisplayed && (isTriggerOnMessage || isNewTriggerAdded && isOnlyDynamicTriggers)
- ComplexMethod:TriggerController.kt$TriggerController$private fun evaluateTrigger(trigger: Trigger): Boolean
+ ComplexMethod:InAppMessagesManager.kt$InAppMessagesManager$private suspend fun fetchMessages(rywData: RywData)
ConstructorParameterNaming:DynamicTriggerController.kt$DynamicTriggerController$private val _session: ISessionService
ConstructorParameterNaming:DynamicTriggerController.kt$DynamicTriggerController$private val _state: InAppStateService
ConstructorParameterNaming:DynamicTriggerController.kt$DynamicTriggerController$private val _time: ITime
@@ -39,6 +39,7 @@
ConstructorParameterNaming:InAppMessagesManager.kt$InAppMessagesManager$private val _displayer: IInAppDisplayer
ConstructorParameterNaming:InAppMessagesManager.kt$InAppMessagesManager$private val _identityModelStore: IdentityModelStore
ConstructorParameterNaming:InAppMessagesManager.kt$InAppMessagesManager$private val _influenceManager: IInfluenceManager
+ ConstructorParameterNaming:InAppMessagesManager.kt$InAppMessagesManager$private val _jwtTokenStore: JwtTokenStore
ConstructorParameterNaming:InAppMessagesManager.kt$InAppMessagesManager$private val _languageContext: ILanguageContext
ConstructorParameterNaming:InAppMessagesManager.kt$InAppMessagesManager$private val _lifecycle: IInAppLifecycleService
ConstructorParameterNaming:InAppMessagesManager.kt$InAppMessagesManager$private val _outcomeEventsController: IOutcomeEventsController
@@ -65,10 +66,12 @@
ForbiddenComment:InAppMessagesManager.kt$InAppMessagesManager$// TODO until we don't fix the activity going forward or back dismissing the IAM, we need to auto dismiss
ForbiddenComment:InAppMessagesManager.kt$InAppMessagesManager$// TODO: Add more action payload preview logs here in future
LongMethod:DynamicTriggerController.kt$DynamicTriggerController$fun dynamicTriggerShouldFire(trigger: Trigger): Boolean
+ LongMethod:InAppMessagesManager.kt$InAppMessagesManager$private suspend fun fetchMessages(rywData: RywData)
LongMethod:InAppRepository.kt$InAppRepository$override suspend fun cleanCachedInAppMessages()
+ LongParameterList:IInAppBackendService.kt$IInAppBackendService$( appId: String, aliasLabel: String, aliasValue: String, subscriptionId: String, rywData: RywData, sessionDurationProvider: () -> Long, jwt: String? = null, )
LongParameterList:IInAppBackendService.kt$IInAppBackendService$( appId: String, subscriptionId: String, variantId: String?, messageId: String, clickId: String?, isFirstClick: Boolean, )
LongParameterList:InAppDisplayer.kt$InAppDisplayer$( private val _applicationService: IApplicationService, private val _lifecycle: IInAppLifecycleService, private val _promptFactory: IInAppMessagePromptFactory, private val _backend: IInAppBackendService, private val _influenceManager: IInfluenceManager, private val _configModelStore: ConfigModelStore, private val _languageContext: ILanguageContext, private val _time: ITime, )
- LongParameterList:InAppMessagesManager.kt$InAppMessagesManager$( private val _applicationService: IApplicationService, private val _sessionService: ISessionService, private val _influenceManager: IInfluenceManager, private val _configModelStore: ConfigModelStore, private val _userManager: IUserManager, private val _identityModelStore: IdentityModelStore, private val _subscriptionManager: ISubscriptionManager, private val _outcomeEventsController: IOutcomeEventsController, private val _state: InAppStateService, private val _prefs: IInAppPreferencesController, private val _repository: IInAppRepository, private val _backend: IInAppBackendService, private val _triggerController: ITriggerController, private val _triggerModelStore: TriggerModelStore, private val _displayer: IInAppDisplayer, private val _lifecycle: IInAppLifecycleService, private val _languageContext: ILanguageContext, private val _time: ITime, private val _consistencyManager: IConsistencyManager, )
+ LongParameterList:InAppMessagesManager.kt$InAppMessagesManager$( private val _applicationService: IApplicationService, private val _sessionService: ISessionService, private val _influenceManager: IInfluenceManager, private val _configModelStore: ConfigModelStore, private val _userManager: IUserManager, private val _identityModelStore: IdentityModelStore, private val _subscriptionManager: ISubscriptionManager, private val _outcomeEventsController: IOutcomeEventsController, private val _state: InAppStateService, private val _prefs: IInAppPreferencesController, private val _repository: IInAppRepository, private val _backend: IInAppBackendService, private val _triggerController: ITriggerController, private val _triggerModelStore: TriggerModelStore, private val _displayer: IInAppDisplayer, private val _lifecycle: IInAppLifecycleService, private val _languageContext: ILanguageContext, private val _time: ITime, private val _consistencyManager: IConsistencyManager, private val _jwtTokenStore: JwtTokenStore, )
LongParameterList:OneSignalAnimate.kt$OneSignalAnimate$( view: View, deltaFromY: Float, deltaToY: Float, duration: Int, interpolator: Interpolator?, animCallback: Animation.AnimationListener?, )
MagicNumber:DraggableRelativeLayout.kt$DraggableRelativeLayout$3
MagicNumber:DraggableRelativeLayout.kt$DraggableRelativeLayout$3000
@@ -85,12 +88,12 @@
MagicNumber:InAppMessageView.kt$InAppMessageView$5
MagicNumber:InAppMessageView.kt$InAppMessageView$8
MagicNumber:InAppMessageView.kt$InAppMessageView$8.0
- MagicNumber:InAppMessageView.kt$InAppMessageView.<no name provided>$5
+ MagicNumber:InAppMessageView.kt$InAppMessageView.<no name provided>$5
MagicNumber:InAppMessagesManager.kt$InAppMessagesManager$1000
MagicNumber:InAppRepository.kt$InAppRepository$1000L
MagicNumber:OneSignalAnimate.kt$OneSignalAnimate$0.5f
MagicNumber:WebViewManager.kt$WebViewManager$3
- NestedBlockDepth:TriggerController.kt$TriggerController$override fun isTriggerOnMessage( message: InAppMessage, triggersKeys: Collection<String>, ): Boolean
+ NestedBlockDepth:TriggerController.kt$TriggerController$override fun isTriggerOnMessage( message: InAppMessage, triggersKeys: Collection<String>, ): Boolean
PrintStackTrace:InAppMessage.kt$InAppMessage$e
PrintStackTrace:InAppMessage.kt$InAppMessage$exception
PrintStackTrace:InAppMessageClickResult.kt$InAppMessageClickResult$e
@@ -100,17 +103,17 @@
PrintStackTrace:InAppMessageTag.kt$InAppMessageTag$e
PrintStackTrace:Trigger.kt$Trigger$exception
PrintStackTrace:WebViewManager.kt$WebViewManager.OSJavaScriptInterface$e
- ReturnCount:DraggableRelativeLayout.kt$DraggableRelativeLayout.<no name provided>$override fun clampViewPositionVertical( child: View, top: Int, dy: Int, ): Int
+ ReturnCount:DraggableRelativeLayout.kt$DraggableRelativeLayout.<no name provided>$override fun clampViewPositionVertical( child: View, top: Int, dy: Int, ): Int
ReturnCount:DynamicTriggerController.kt$DynamicTriggerController$fun dynamicTriggerShouldFire(trigger: Trigger): Boolean
ReturnCount:InAppBackendService.kt$InAppBackendService$override suspend fun getIAMData( appId: String, messageId: String, variantId: String?, ): GetIAMDataResponse
- ReturnCount:InAppBackendService.kt$InAppBackendService$private suspend fun attemptFetchWithRetries( baseUrl: String, rywData: RywData, sessionDurationProvider: () -> Long, ): List<InAppMessage>?
+ ReturnCount:InAppBackendService.kt$InAppBackendService$private suspend fun attemptFetchWithRetries( baseUrl: String, rywData: RywData, sessionDurationProvider: () -> Long, jwt: String? = null, ): List<InAppMessage>?
ReturnCount:InAppHydrator.kt$InAppHydrator$fun hydrateIAMMessageContent(jsonObject: JSONObject): InAppMessageContent?
ReturnCount:InAppMessage.kt$InAppMessage$private fun parseEndTimeJson(json: JSONObject): Date?
ReturnCount:InAppMessagePreviewHandler.kt$InAppMessagePreviewHandler$private fun inAppPreviewPushUUID(payload: JSONObject): String?
ReturnCount:InAppMessagesManager.kt$InAppMessagesManager$override fun onMessageWasDisplayed(message: InAppMessage)
ReturnCount:InAppMessagesManager.kt$InAppMessagesManager$private suspend fun fetchMessages(rywData: RywData)
ReturnCount:TriggerController.kt$TriggerController$override fun evaluateMessageTriggers(message: InAppMessage): Boolean
- ReturnCount:TriggerController.kt$TriggerController$override fun isTriggerOnMessage( message: InAppMessage, triggersKeys: Collection<String>, ): Boolean
+ ReturnCount:TriggerController.kt$TriggerController$override fun isTriggerOnMessage( message: InAppMessage, triggersKeys: Collection<String>, ): Boolean
ReturnCount:TriggerController.kt$TriggerController$override fun messageHasOnlyDynamicTriggers(message: InAppMessage): Boolean
ReturnCount:TriggerController.kt$TriggerController$private fun evaluateTrigger(trigger: Trigger): Boolean
ReturnCount:TriggerController.kt$TriggerController$private fun triggerMatchesFlex( triggerValue: Any?, deviceValue: Any, operator: Trigger.OSTriggerOperator, ): Boolean
diff --git a/OneSignalSDK/detekt/detekt-baseline-notifications.xml b/OneSignalSDK/detekt/detekt-baseline-notifications.xml
index 8ee6e8c589..104b3ef175 100644
--- a/OneSignalSDK/detekt/detekt-baseline-notifications.xml
+++ b/OneSignalSDK/detekt/detekt-baseline-notifications.xml
@@ -137,28 +137,20 @@
LongParameterList:INotificationGenerationWorkManager.kt$INotificationGenerationWorkManager$( context: Context, osNotificationId: String, androidNotificationId: Int, jsonPayload: JSONObject?, timestamp: Long, isRestoring: Boolean, isHighPriority: Boolean, )
LongParameterList:INotificationRepository.kt$INotificationRepository$( id: String, groupId: String?, collapseKey: String?, shouldDismissIdenticals: Boolean, isOpened: Boolean, androidId: Int, title: String?, body: String?, expireTime: Long, jsonPayload: String, )
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, )
- 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 = { _backend.updateNotificationAsOpened( appId, notificationId, subscriptionId, deviceType, ) }, onError = { ex -> if (ex is BackendException) { Logging.error("Notification opened confirmation failed with statusCode: ${ex.statusCode} response: ${ex.response}") } else { Logging.error("Unexpected error in notification opened confirmation", ex) } }, ) }
+ 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 = { _backend.updateNotificationAsOpened( 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) } }, ) }
MagicNumber:FirebaseAnalyticsTracker.kt$FirebaseAnalyticsTracker$1000
MagicNumber:FirebaseAnalyticsTracker.kt$FirebaseAnalyticsTracker$30
MagicNumber:FirebaseAnalyticsTracker.kt$FirebaseAnalyticsTracker$60
MagicNumber:Notification.kt$Notification$1000
MagicNumber:NotificationBundleProcessor.kt$NotificationBundleProcessor$1000L
- MagicNumber:NotificationBundleProcessor.kt$NotificationBundleProcessor$9
MagicNumber:NotificationChannelManager.kt$NotificationChannelManager$16
- MagicNumber:NotificationChannelManager.kt$NotificationChannelManager$3
- MagicNumber:NotificationChannelManager.kt$NotificationChannelManager$5
MagicNumber:NotificationChannelManager.kt$NotificationChannelManager$6
- MagicNumber:NotificationChannelManager.kt$NotificationChannelManager$7
- MagicNumber:NotificationChannelManager.kt$NotificationChannelManager$9
MagicNumber:NotificationDisplayBuilder.kt$NotificationDisplayBuilder$1000L
MagicNumber:NotificationDisplayBuilder.kt$NotificationDisplayBuilder$16
MagicNumber:NotificationDisplayBuilder.kt$NotificationDisplayBuilder$2000
MagicNumber:NotificationDisplayBuilder.kt$NotificationDisplayBuilder$3
- MagicNumber:NotificationDisplayBuilder.kt$NotificationDisplayBuilder$4
MagicNumber:NotificationDisplayBuilder.kt$NotificationDisplayBuilder$5000
MagicNumber:NotificationDisplayBuilder.kt$NotificationDisplayBuilder$6
- MagicNumber:NotificationDisplayBuilder.kt$NotificationDisplayBuilder$7
- MagicNumber:NotificationDisplayBuilder.kt$NotificationDisplayBuilder$9
MagicNumber:NotificationDisplayer.kt$NotificationDisplayer$16
MagicNumber:NotificationDisplayer.kt$NotificationDisplayer$3
MagicNumber:NotificationDisplayer.kt$NotificationDisplayer$5000
@@ -166,6 +158,9 @@
MagicNumber:NotificationGenerationProcessor.kt$NotificationGenerationProcessor$1000L
MagicNumber:NotificationGenerationWorkManager.kt$NotificationGenerationWorkManager.NotificationGenerationWorker$1000L
MagicNumber:NotificationHelper.kt$NotificationHelper$10
+ MagicNumber:NotificationPriorityMapper.kt$NotificationPriorityMapper$3
+ MagicNumber:NotificationPriorityMapper.kt$NotificationPriorityMapper$5
+ MagicNumber:NotificationPriorityMapper.kt$NotificationPriorityMapper$7
MagicNumber:NotificationQueryHelper.kt$NotificationQueryHelper$1000L
MagicNumber:NotificationQueryHelper.kt$NotificationQueryHelper$604800L
MagicNumber:NotificationRepository.kt$NotificationRepository$1000L
@@ -194,8 +189,6 @@
ReturnCount:GenerateNotificationOpenIntent.kt$GenerateNotificationOpenIntent$private fun getIntentAppOpen(): Intent?
ReturnCount:NotificationChannelManager.kt$NotificationChannelManager$override fun createNotificationChannel(notificationJob: NotificationGenerationJob): String
ReturnCount:NotificationChannelManager.kt$NotificationChannelManager$override fun processChannelList(list: JSONArray?)
- ReturnCount:NotificationChannelManager.kt$NotificationChannelManager$private fun priorityToImportance(priority: Int): Int
- ReturnCount:NotificationDisplayBuilder.kt$NotificationDisplayBuilder$private fun convertOSToAndroidPriority(priority: Int): Int
ReturnCount:NotificationDisplayBuilder.kt$NotificationDisplayBuilder$private fun getAccentColor(fcmJson: JSONObject): BigInteger?
ReturnCount:NotificationDisplayBuilder.kt$NotificationDisplayBuilder$private fun getBitmapFromAssetsOrResourceName(bitmapStr: String): Bitmap?
ReturnCount:NotificationDisplayBuilder.kt$NotificationDisplayBuilder$private fun getResourceIcon(iconName: String?): Int
@@ -213,6 +206,8 @@
ReturnCount:NotificationHelper.kt$NotificationHelper$fun getNotificationIdFromFCMJson(fcmJson: JSONObject?): String?
ReturnCount:NotificationLifecycleService.kt$NotificationLifecycleService$private fun shouldInitDirectSessionFromNotificationOpen(context: Activity): Boolean
ReturnCount:NotificationPermissionController.kt$NotificationPermissionController$override suspend fun prompt(fallbackToSettings: Boolean): Boolean
+ ReturnCount:NotificationPriorityMapper.kt$NotificationPriorityMapper$fun toAndroidImportance(osPriority: Int): Int
+ ReturnCount:NotificationPriorityMapper.kt$NotificationPriorityMapper$fun toAndroidPriority(osPriority: Int): Int
ReturnCount:NotificationRestoreProcessor.kt$NotificationRestoreProcessor$private fun getVisibleNotifications(): List<Int>?
ReturnCount:NotificationRestoreWorkManager.kt$NotificationRestoreWorkManager.NotificationRestoreWorker$override suspend fun doWork(): Result
ReturnCount:NotificationSummaryManager.kt$NotificationSummaryManager$private suspend fun internalUpdateSummaryNotificationAfterChildRemoved( group: String, dismissed: Boolean, )
@@ -247,6 +242,7 @@
TooGenericExceptionCaught:NotificationGenerationProcessor.kt$NotificationGenerationProcessor$t: Throwable
TooGenericExceptionCaught:NotificationHelper.kt$NotificationHelper$e: Throwable
TooGenericExceptionCaught:NotificationHelper.kt$NotificationHelper$t: Throwable
+ TooGenericExceptionCaught:NotificationLifecycleService.kt$NotificationLifecycleService$e: Exception
TooGenericExceptionCaught:NotificationLimitManager.kt$NotificationLimitManager$t: Throwable
TooGenericExceptionCaught:NotificationRepository.kt$NotificationRepository$t: Throwable
TooGenericExceptionCaught:NotificationRestoreProcessor.kt$NotificationRestoreProcessor$t: Throwable
diff --git a/OneSignalSDK/detekt/detekt-baseline-otel.xml b/OneSignalSDK/detekt/detekt-baseline-otel.xml
new file mode 100644
index 0000000000..751e432022
--- /dev/null
+++ b/OneSignalSDK/detekt/detekt-baseline-otel.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ LongParameterList:OtelLoggingHelper.kt$OtelLoggingHelper$( telemetry: IOtelOpenTelemetryRemote, level: String, message: String, exceptionType: String? = null, exceptionMessage: String? = null, exceptionStacktrace: String? = null, )
+ ReturnCount:OtelConfigRemoteOneSignal.kt$OtelConfigRemoteOneSignal.ExporterLoggingConfig.LoggingLogRecordExporter$@Suppress("TooGenericExceptionCaught") private fun resolveHttpFailureMessage(throwable: Throwable?): String
+ TooGenericExceptionCaught:OtelCrashHandler.kt$OtelCrashHandler$t: Throwable
+ UndocumentedPublicFunction:IOtelCrashReporter.kt$IOtelCrashReporter$suspend fun saveCrash(thread: Thread, throwable: Throwable)
+
+
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/IOneSignal.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/IOneSignal.kt
index e20ddfc2ac..477f7e90ac 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/IOneSignal.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/IOneSignal.kt
@@ -226,4 +226,31 @@ interface IOneSignal {
* Logout the current user (suspend version).
*/
suspend fun logoutSuspend()
+
+ /**
+ * Update the JWT bearer token for a user identified by [externalId]. Call this when
+ * a token is about to expire or after receiving an [IUserJwtInvalidatedListener] callback.
+ *
+ * @param externalId The external ID of the user whose token is being updated.
+ * @param token The new JWT bearer token.
+ */
+ fun updateUserJwt(
+ externalId: String,
+ token: String,
+ )
+
+ /**
+ * Add a listener that will be called when a user's JWT is invalidated (e.g. expired
+ * or rejected by the server). Use this to provide a fresh token via [updateUserJwt].
+ *
+ * @param listener The listener to add.
+ */
+ fun addUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener)
+
+ /**
+ * Remove a previously added [IUserJwtInvalidatedListener].
+ *
+ * @param listener The listener to remove.
+ */
+ fun removeUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener)
}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/IUserJwtInvalidatedListener.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/IUserJwtInvalidatedListener.kt
new file mode 100644
index 0000000000..82cc6e1d7b
--- /dev/null
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/IUserJwtInvalidatedListener.kt
@@ -0,0 +1,15 @@
+package com.onesignal
+
+/**
+ * Implement this interface and provide an instance to [OneSignal.addUserJwtInvalidatedListener]
+ * in order to receive control when the JWT for the current user is invalidated.
+ *
+ */
+interface IUserJwtInvalidatedListener {
+ /**
+ * Called when the JWT is invalidated
+ *
+ * @param event The user JWT that expired.
+ */
+ fun onUserJwtInvalidated(event: UserJwtInvalidatedEvent)
+}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/OneSignal.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/OneSignal.kt
index 708bbe08f8..55c343631f 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/OneSignal.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/OneSignal.kt
@@ -343,6 +343,42 @@ object OneSignal {
@JvmStatic
fun logout() = oneSignal.logout()
+ /**
+ * Update the JWT bearer token for a user identified by [externalId]. Call this when
+ * a token is about to expire or after receiving an [IUserJwtInvalidatedListener] callback.
+ *
+ * @param externalId The external ID of the user whose token is being updated.
+ * @param token The new JWT bearer token.
+ */
+ @JvmStatic
+ fun updateUserJwt(
+ externalId: String,
+ token: String,
+ ) {
+ oneSignal.updateUserJwt(externalId, token)
+ }
+
+ /**
+ * Add a listener that will be called when a user's JWT is invalidated (e.g. expired
+ * or rejected by the server). Use this to provide a fresh token via [updateUserJwt].
+ *
+ * @param listener The listener to add.
+ */
+ @JvmStatic
+ fun addUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) {
+ oneSignal.addUserJwtInvalidatedListener(listener)
+ }
+
+ /**
+ * Remove a previously added [IUserJwtInvalidatedListener].
+ *
+ * @param listener The listener to remove.
+ */
+ @JvmStatic
+ fun removeUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) {
+ oneSignal.removeUserJwtInvalidatedListener(listener)
+ }
+
private val oneSignal: IOneSignal by lazy {
OneSignalImp()
}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/UserJwtInvalidatedEvent.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/UserJwtInvalidatedEvent.kt
new file mode 100644
index 0000000000..9c7ddcb87b
--- /dev/null
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/UserJwtInvalidatedEvent.kt
@@ -0,0 +1,10 @@
+package com.onesignal
+
+/**
+ * The event passed into [IUserJwtInvalidatedListener.onUserJwtInvalidated], it provides access
+ * to the external ID whose JWT has just been invalidated.
+ *
+ */
+class UserJwtInvalidatedEvent(
+ val externalId: String,
+)
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/CoreModule.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/CoreModule.kt
index 9d34231d63..260a830c81 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/CoreModule.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/CoreModule.kt
@@ -10,6 +10,7 @@ import com.onesignal.core.internal.background.IBackgroundManager
import com.onesignal.core.internal.background.impl.BackgroundManager
import com.onesignal.core.internal.config.ConfigModelStore
import com.onesignal.core.internal.config.impl.ConfigModelStoreListener
+import com.onesignal.core.internal.config.impl.IdentityVerificationService
import com.onesignal.core.internal.database.IDatabaseProvider
import com.onesignal.core.internal.database.impl.DatabaseProvider
import com.onesignal.core.internal.device.IDeviceService
@@ -42,6 +43,7 @@ import com.onesignal.location.ILocationManager
import com.onesignal.location.internal.MisconfiguredLocationManager
import com.onesignal.notifications.INotificationsManager
import com.onesignal.notifications.internal.MisconfiguredNotificationsManager
+import com.onesignal.user.internal.identity.JwtTokenStore
internal class CoreModule : IModule {
override fun register(builder: ServiceBuilder) {
@@ -63,6 +65,10 @@ internal class CoreModule : IModule {
builder.register().provides()
builder.register().provides()
+ // Identity Verification
+ builder.register().provides()
+ builder.register().provides()
+
// Operations
builder.register().provides()
builder.register()
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/backend/impl/ParamsBackendService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/backend/impl/ParamsBackendService.kt
index ec0af86055..273946ef0b 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/backend/impl/ParamsBackendService.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/backend/impl/ParamsBackendService.kt
@@ -84,8 +84,7 @@ internal class ParamsBackendService(
return ParamsObject(
googleProjectNumber = responseJson.safeString("android_sender_id"),
enterprise = responseJson.safeBool("enterp"),
- // TODO: New
- useIdentityVerification = responseJson.safeBool("require_ident_auth"),
+ useIdentityVerification = responseJson.safeBool("jwt_required") ?: false,
notificationChannels = responseJson.optJSONArray("chnl_lst"),
firebaseAnalytics = responseJson.safeBool("fba"),
restoreTTLFilter = responseJson.safeBool("restore_ttl_filter"),
@@ -95,7 +94,6 @@ internal class ParamsBackendService(
unsubscribeWhenNotificationsDisabled = responseJson.safeBool("unsubscribe_on_notifications_disabled"),
locationShared = responseJson.safeBool("location_shared"),
requiresUserPrivacyConsent = responseJson.safeBool("requires_user_privacy_consent"),
- // TODO: New
opRepoExecutionInterval = responseJson.safeLong("oprepo_execution_interval"),
features = features,
influenceParams = influenceParams ?: InfluenceParamsObject(),
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt
index a88739e05e..86ac5f56bf 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt
@@ -237,12 +237,15 @@ class ConfigModel : Model() {
}
/**
- * Whether SMS auth hash should be used.
+ * Whether identity verification (JWT) is required for this application.
+ * - `null` = unknown (remote params haven't arrived yet; all operations are held)
+ * - `false` = explicitly disabled (SDK behaves as today, no JWT gating)
+ * - `true` = enabled (operations require a valid JWT, anonymous users are blocked)
*/
- var useIdentityVerification: Boolean
- get() = getBooleanProperty(::useIdentityVerification.name) { false }
+ var useIdentityVerification: Boolean?
+ get() = getOptBooleanProperty(::useIdentityVerification.name)
set(value) {
- setBooleanProperty(::useIdentityVerification.name, value)
+ setOptBooleanProperty(::useIdentityVerification.name, value)
}
/**
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/impl/IdentityVerificationService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/impl/IdentityVerificationService.kt
new file mode 100644
index 0000000000..171be38190
--- /dev/null
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/impl/IdentityVerificationService.kt
@@ -0,0 +1,66 @@
+package com.onesignal.core.internal.config.impl
+
+import com.onesignal.common.modeling.ISingletonModelStoreChangeHandler
+import com.onesignal.common.modeling.ModelChangeTags
+import com.onesignal.common.modeling.ModelChangedArgs
+import com.onesignal.core.internal.config.ConfigModel
+import com.onesignal.core.internal.config.ConfigModelStore
+import com.onesignal.core.internal.operations.IOperationRepo
+import com.onesignal.core.internal.startup.IStartableService
+import com.onesignal.debug.internal.logging.Logging
+import com.onesignal.user.internal.UserManager
+import com.onesignal.user.internal.identity.IdentityModelStore
+import com.onesignal.user.internal.identity.JwtTokenStore
+
+/**
+ * Reacts to the identity-verification remote param arriving via config HYDRATE.
+ *
+ * - When IV transitions from unknown (null) to true: purges anonymous operations.
+ * - When IV transitions from unknown (null) to any value: wakes the operation queue.
+ * - On beta migration: if IV=true and the current user has an externalId but no JWT,
+ * fires [UserJwtInvalidatedEvent] so the developer provides a fresh token.
+ */
+internal class IdentityVerificationService(
+ private val _configModelStore: ConfigModelStore,
+ private val _operationRepo: IOperationRepo,
+ private val _identityModelStore: IdentityModelStore,
+ private val _jwtTokenStore: JwtTokenStore,
+ private val _userManager: UserManager,
+) : IStartableService, ISingletonModelStoreChangeHandler {
+ override fun start() {
+ _configModelStore.subscribe(this)
+ _operationRepo.setJwtInvalidatedHandler { externalId ->
+ _userManager.fireJwtInvalidated(externalId)
+ }
+ }
+
+ override fun onModelReplaced(
+ model: ConfigModel,
+ tag: String,
+ ) {
+ if (tag != ModelChangeTags.HYDRATE) return
+
+ val useIV = model.useIdentityVerification
+
+ if (useIV == true) {
+ Logging.debug("IdentityVerificationService: IV enabled, purging anonymous operations")
+ _operationRepo.removeOperationsWithoutExternalId()
+
+ val externalId = _identityModelStore.model.externalId
+ if (externalId != null && _jwtTokenStore.getJwt(externalId) == null) {
+ Logging.debug("IdentityVerificationService: IV enabled but no JWT for $externalId, firing invalidated event")
+ _userManager.fireJwtInvalidated(externalId)
+ }
+ }
+
+ _operationRepo.forceExecuteOperations()
+ }
+
+ override fun onModelUpdated(
+ args: ModelChangedArgs,
+ tag: String,
+ ) {
+ // Individual property updates are not expected for remote params;
+ // ConfigModelStoreListener replaces the entire model on HYDRATE.
+ }
+}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/HttpClient.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/HttpClient.kt
index d1ea2036c2..f7c01b843e 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/HttpClient.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/HttpClient.kt
@@ -159,18 +159,6 @@ internal class HttpClient(
con.doOutput = true
}
- logHTTPSent(con.requestMethod, con.url, jsonBody, con.requestProperties)
-
- if (jsonBody != null) {
- val strJsonBody = JSONUtils.toUnescapedEUIDString(jsonBody)
- val sendBytes = strJsonBody.toByteArray(charset("UTF-8"))
- con.setFixedLengthStreamingMode(sendBytes.size)
- val outputStream = con.outputStream
- outputStream.write(sendBytes)
- }
-
- // H E A D E R S
-
if (headers?.cacheKey != null) {
val eTag =
_prefs.getString(
@@ -195,6 +183,20 @@ internal class HttpClient(
con.setRequestProperty("OneSignal-Session-Duration", headers.sessionDuration.toString())
}
+ if (headers?.jwt != null) {
+ con.setRequestProperty("Authorization", "Bearer ${headers.jwt}")
+ }
+
+ logHTTPSent(con.requestMethod, con.url, jsonBody, con.requestProperties)
+
+ if (jsonBody != null) {
+ val strJsonBody = JSONUtils.toUnescapedEUIDString(jsonBody)
+ val sendBytes = strJsonBody.toByteArray(charset("UTF-8"))
+ con.setFixedLengthStreamingMode(sendBytes.size)
+ val outputStream = con.outputStream
+ outputStream.write(sendBytes)
+ }
+
// Network request is made from getResponseCode()
httpResponse = con.responseCode
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/OptionalHeaders.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/OptionalHeaders.kt
index f566fd04fc..8a0f3e7c95 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/OptionalHeaders.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/OptionalHeaders.kt
@@ -17,4 +17,9 @@ data class OptionalHeaders(
* Used to track delay between session start and request
*/
val sessionDuration: Long? = null,
+ /**
+ * JWT bearer token for identity verification. When non-null, sent as
+ * `Authorization: Bearer ` on the request.
+ */
+ val jwt: String? = null,
)
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/IOperationRepo.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/IOperationRepo.kt
index d2dceea5c3..2f8a8ac8fe 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/IOperationRepo.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/IOperationRepo.kt
@@ -42,6 +42,20 @@ interface IOperationRepo {
suspend fun awaitInitialized()
fun forceExecuteOperations()
+
+ /**
+ * Remove all queued operations that have no externalId (anonymous operations).
+ * Used by IdentityVerificationService when identity verification is enabled to
+ * purge operations that cannot be executed without an authenticated user.
+ */
+ fun removeOperationsWithoutExternalId()
+
+ /**
+ * Register a handler to be called when a runtime 401 Unauthorized response
+ * invalidates a JWT. This allows the caller to notify the developer so they
+ * can supply a fresh token via [OneSignal.updateUserJwt].
+ */
+ fun setJwtInvalidatedHandler(handler: ((String) -> Unit)?)
}
// Extension function so the syntax containsInstanceOf() can be used over
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/Operation.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/Operation.kt
index 76f51994ab..8227ebb877 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/Operation.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/Operation.kt
@@ -16,6 +16,18 @@ abstract class Operation(name: String) : Model() {
setStringProperty(::name.name, value)
}
+ /**
+ * The external ID of the user this operation belongs to. Used by [IOperationRepo] to look up
+ * the correct JWT when identity verification is enabled, and to gate anonymous operations.
+ * Stamped automatically by [IOperationRepo] at enqueue time from the current identity model
+ * when not already set by the concrete operation's constructor.
+ */
+ var externalId: String?
+ get() = getOptStringProperty(::externalId.name)
+ set(value) {
+ setOptStringProperty(::externalId.name, value)
+ }
+
init {
this.name = name
}
@@ -49,6 +61,13 @@ abstract class Operation(name: String) : Model() {
*/
abstract val canStartExecute: Boolean
+ /**
+ * Whether this operation requires a valid JWT when identity verification is enabled.
+ * Override to return `false` for operations whose backend endpoint does not require
+ * a JWT (e.g. subscription updates).
+ */
+ open val requiresJwt: Boolean get() = true
+
/**
* Called when an operation has resolved a local ID to a backend ID (i.e. successfully
* created a backend resource). Any IDs within the operation that could be local IDs should
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt
index 9b39566d17..03259156ce 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt
@@ -11,6 +11,8 @@ import com.onesignal.core.internal.startup.IStartableService
import com.onesignal.core.internal.time.ITime
import com.onesignal.debug.LogLevel
import com.onesignal.debug.internal.logging.Logging
+import com.onesignal.user.internal.identity.IdentityModelStore
+import com.onesignal.user.internal.identity.JwtTokenStore
import com.onesignal.user.internal.operations.impl.states.NewRecordsState
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
@@ -28,6 +30,8 @@ internal class OperationRepo(
private val _configModelStore: ConfigModelStore,
private val _time: ITime,
private val _newRecordState: NewRecordsState,
+ private val _jwtTokenStore: JwtTokenStore,
+ private val _identityModelStore: IdentityModelStore,
) : IOperationRepo, IStartableService {
internal class OperationQueueItem(
val operation: Operation,
@@ -40,6 +44,9 @@ internal class OperationRepo(
}
}
+ @Volatile
+ private var _jwtInvalidatedHandler: ((String) -> Unit)? = null
+
internal class LoopWaiterMessage(
val force: Boolean,
val previousWaitedTime: Long = 0,
@@ -123,6 +130,7 @@ internal class OperationRepo(
Logging.log(LogLevel.DEBUG, "OperationRepo.enqueue(operation: $operation, flush: $flush)")
operation.id = UUID.randomUUID().toString()
+ stampExternalId(operation)
scope.launch {
internalEnqueue(OperationQueueItem(operation, bucket = enqueueIntoBucket), flush, true)
}
@@ -135,6 +143,7 @@ internal class OperationRepo(
Logging.log(LogLevel.DEBUG, "OperationRepo.enqueueAndWait(operation: $operation, force: $flush)")
operation.id = UUID.randomUUID().toString()
+ stampExternalId(operation)
val waiter = WaiterWithValue()
scope.launch {
internalEnqueue(OperationQueueItem(operation, waiter, bucket = enqueueIntoBucket), flush, true)
@@ -148,6 +157,18 @@ internal class OperationRepo(
*
* @returns true if the OperationQueueItem was added, false if not
*/
+ /**
+ * Capture the externalId from the current identity model onto the operation
+ * synchronously on the caller's thread, before the async enqueue coroutine runs.
+ * Operations that already set externalId in their constructor (e.g. LoginUserOperation)
+ * are left unchanged.
+ */
+ private fun stampExternalId(operation: Operation) {
+ if (operation.externalId == null) {
+ operation.externalId = _identityModelStore.model.externalId
+ }
+ }
+
private fun internalEnqueue(
queueItem: OperationQueueItem,
flush: Boolean,
@@ -188,7 +209,8 @@ internal class OperationRepo(
}
val ops = getNextOps(executeBucket)
- Logging.debug("processQueueForever:ops:\n$ops")
+ val queueSnapshot = synchronized(queue) { queue.toList() }
+ Logging.debug("processQueueForever:ops:\n$ops\nqueue(${queueSnapshot.size}):\n$queueSnapshot")
if (ops != null) {
executeOperations(ops)
@@ -268,7 +290,21 @@ internal class OperationRepo(
ops.forEach { _operationModelStore.remove(it.operation.id) }
ops.forEach { it.waiter?.wake(true) }
}
- ExecutionResult.FAIL_UNAUTHORIZED, // TODO: Need to provide callback for app to reset JWT. For now, fail with no retry.
+ ExecutionResult.FAIL_UNAUTHORIZED -> {
+ val externalId = startingOp.operation.externalId
+ if (externalId != null) {
+ _jwtTokenStore.invalidateJwt(externalId)
+ _jwtInvalidatedHandler?.invoke(externalId)
+ Logging.warn("Operation execution failed with 401 Unauthorized, JWT invalidated for user: $externalId. Operations re-queued.")
+ synchronized(queue) {
+ ops.reversed().forEach { queue.add(0, it) }
+ }
+ } else {
+ Logging.warn("Operation execution failed with 401 Unauthorized for anonymous user. Operations dropped.")
+ ops.forEach { _operationModelStore.remove(it.operation.id) }
+ ops.forEach { it.waiter?.wake(false) }
+ }
+ }
ExecutionResult.FAIL_NORETRY,
ExecutionResult.FAIL_CONFLICT,
-> {
@@ -312,9 +348,13 @@ internal class OperationRepo(
// if there are operations provided on the result, we need to enqueue them at the
// beginning of the queue.
if (response.operations != null) {
+ val parentExternalId = startingOp.operation.externalId
synchronized(queue) {
for (op in response.operations.reversed()) {
op.id = UUID.randomUUID().toString()
+ if (op.externalId == null && parentExternalId != null) {
+ op.externalId = parentExternalId
+ }
val queueItem = OperationQueueItem(op, bucket = 0)
queue.add(0, queueItem)
_operationModelStore.add(0, queueItem.operation)
@@ -372,12 +412,16 @@ internal class OperationRepo(
}
internal fun getNextOps(bucketFilter: Int): List? {
+ val iv = _configModelStore.model.useIdentityVerification
+ if (iv == null) return null
+
return synchronized(queue) {
val startingOp =
queue.firstOrNull {
it.operation.canStartExecute &&
_newRecordState.canAccess(it.operation.applyToRecordId) &&
- it.bucket <= bucketFilter
+ it.bucket <= bucketFilter &&
+ hasValidJwtIfRequired(iv, it.operation)
}
if (startingOp != null) {
@@ -389,6 +433,16 @@ internal class OperationRepo(
}
}
+ private fun hasValidJwtIfRequired(
+ iv: Boolean,
+ op: Operation,
+ ): Boolean {
+ if (!iv) return true
+ if (!op.requiresJwt) return true
+ val externalId = op.externalId ?: return false
+ return _jwtTokenStore.getJwt(externalId) != null
+ }
+
/**
* Given a starting operation, find and remove from the queue all other operations that
* can be executed along with the starting operation. The full list of operations, with
@@ -450,6 +504,32 @@ internal class OperationRepo(
index = 0,
)
}
+
+ val activeExternalIds =
+ synchronized(queue) {
+ queue.mapNotNull { it.operation.externalId }.toMutableSet()
+ }
+ _identityModelStore.model.externalId?.let { activeExternalIds.add(it) }
+ _jwtTokenStore.pruneToExternalIds(activeExternalIds)
+
initialized.complete(Unit)
}
+
+ override fun removeOperationsWithoutExternalId() {
+ synchronized(queue) {
+ val toRemove = queue.filter { it.operation.externalId == null }
+ toRemove.forEach {
+ queue.remove(it)
+ _operationModelStore.remove(it.operation.id)
+ it.waiter?.wake(false)
+ }
+ if (toRemove.isNotEmpty()) {
+ Logging.debug("OperationRepo: removed ${toRemove.size} anonymous operations (no externalId)")
+ }
+ }
+ }
+
+ override fun setJwtInvalidatedHandler(handler: ((String) -> Unit)?) {
+ _jwtInvalidatedHandler = handler
+ }
}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/preferences/IPreferencesService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/preferences/IPreferencesService.kt
index f4d4b92a5d..0c9f47c517 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/preferences/IPreferencesService.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/preferences/IPreferencesService.kt
@@ -272,6 +272,13 @@ object PreferenceOneSignalKeys {
*/
const val PREFS_OS_IAM_LAST_DISMISSED_TIME = "PREFS_OS_IAM_LAST_DISMISSED_TIME"
+ // Identity Verification
+
+ /**
+ * (String) JSON map of externalId -> JWT token for identity verification.
+ */
+ const val PREFS_OS_JWT_TOKENS = "PREFS_OS_JWT_TOKENS"
+
// Models
/**
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OneSignalImp.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OneSignalImp.kt
index 5cd9cb9177..875d32042d 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OneSignalImp.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OneSignalImp.kt
@@ -2,6 +2,7 @@ package com.onesignal.internal
import android.content.Context
import com.onesignal.IOneSignal
+import com.onesignal.IUserJwtInvalidatedListener
import com.onesignal.common.AndroidUtils
import com.onesignal.common.DeviceUtils
import com.onesignal.common.OneSignalUtils
@@ -35,8 +36,10 @@ import com.onesignal.user.IUserManager
import com.onesignal.user.UserModule
import com.onesignal.user.internal.LoginHelper
import com.onesignal.user.internal.LogoutHelper
+import com.onesignal.user.internal.UserManager
import com.onesignal.user.internal.UserSwitcher
import com.onesignal.user.internal.identity.IdentityModelStore
+import com.onesignal.user.internal.identity.JwtTokenStore
import com.onesignal.user.internal.properties.PropertiesModelStore
import com.onesignal.user.internal.resolveAppId
import com.onesignal.user.internal.subscriptions.SubscriptionModelStore
@@ -142,6 +145,7 @@ internal class OneSignalImp(
private val propertiesModelStore: PropertiesModelStore by lazy { services.getService() }
private val subscriptionModelStore: SubscriptionModelStore by lazy { services.getService() }
private val preferencesService: IPreferencesService by lazy { services.getService() }
+ private val jwtTokenStore: JwtTokenStore by lazy { services.getService() }
private val listOfModules =
listOf(
"com.onesignal.notifications.NotificationsModule",
@@ -220,6 +224,7 @@ internal class OneSignalImp(
userSwitcher = userSwitcher,
operationRepo = operationRepo,
configModel = configModel,
+ jwtTokenStore = jwtTokenStore,
lock = loginLogoutLock,
)
}
@@ -230,6 +235,7 @@ internal class OneSignalImp(
userSwitcher = userSwitcher,
operationRepo = operationRepo,
configModel = configModel,
+ subscriptionModelStore = subscriptionModelStore,
lock = loginLogoutLock,
)
}
@@ -409,6 +415,23 @@ internal class OneSignalImp(
}
}
+ override fun updateUserJwt(
+ externalId: String,
+ token: String,
+ ) {
+ Logging.log(LogLevel.DEBUG, "updateUserJwt(externalId: $externalId)")
+ jwtTokenStore.putJwt(externalId, token)
+ operationRepo.forceExecuteOperations()
+ }
+
+ override fun addUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) {
+ services.getService().addJwtInvalidatedListener(listener)
+ }
+
+ override fun removeUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) {
+ services.getService().removeJwtInvalidatedListener(listener)
+ }
+
override fun hasService(c: Class): Boolean = services.hasService(c)
override fun getService(c: Class): T = services.getService(c)
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/UserModule.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/UserModule.kt
index be55228756..0b92fb85bc 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/UserModule.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/UserModule.kt
@@ -75,7 +75,7 @@ internal class UserModule : IModule {
builder.register().provides()
builder.register().provides()
builder.register().provides()
- builder.register().provides()
+ builder.register().provides().provides()
builder.register().provides()
builder.register().provides()
builder.register().provides()
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/LoginHelper.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/LoginHelper.kt
index 15939441ba..cc9bb14daf 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/LoginHelper.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/LoginHelper.kt
@@ -4,6 +4,7 @@ import com.onesignal.core.internal.config.ConfigModel
import com.onesignal.core.internal.operations.IOperationRepo
import com.onesignal.debug.internal.logging.Logging
import com.onesignal.user.internal.identity.IdentityModelStore
+import com.onesignal.user.internal.identity.JwtTokenStore
import com.onesignal.user.internal.operations.LoginUserOperation
class LoginHelper(
@@ -11,6 +12,7 @@ class LoginHelper(
private val userSwitcher: UserSwitcher,
private val operationRepo: IOperationRepo,
private val configModel: ConfigModel,
+ private val jwtTokenStore: JwtTokenStore,
private val lock: Any,
) {
suspend fun login(
@@ -26,10 +28,13 @@ class LoginHelper(
currentIdentityOneSignalId = identityModelStore.model.onesignalId
if (currentIdentityExternalId == externalId) {
+ jwtTokenStore.putJwt(externalId, jwtBearerToken)
+ operationRepo.forceExecuteOperations()
return
}
- // TODO: Set JWT Token for all future requests.
+ jwtTokenStore.putJwt(externalId, jwtBearerToken)
+
userSwitcher.createAndSwitchToNewUser { identityModel, _ ->
identityModel.externalId = externalId
}
@@ -37,13 +42,20 @@ class LoginHelper(
newIdentityOneSignalId = identityModelStore.model.onesignalId
}
+ val existingOneSignalId =
+ if (configModel.useIdentityVerification == true) {
+ null
+ } else {
+ if (currentIdentityExternalId == null) currentIdentityOneSignalId else null
+ }
+
val result =
operationRepo.enqueueAndWait(
LoginUserOperation(
configModel.appId,
newIdentityOneSignalId,
externalId,
- if (currentIdentityExternalId == null) currentIdentityOneSignalId else null,
+ existingOneSignalId,
),
)
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/LogoutHelper.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/LogoutHelper.kt
index 8d9015c612..ebe105c585 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/LogoutHelper.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/LogoutHelper.kt
@@ -4,12 +4,14 @@ import com.onesignal.core.internal.config.ConfigModel
import com.onesignal.core.internal.operations.IOperationRepo
import com.onesignal.user.internal.identity.IdentityModelStore
import com.onesignal.user.internal.operations.LoginUserOperation
+import com.onesignal.user.internal.subscriptions.SubscriptionModelStore
class LogoutHelper(
private val identityModelStore: IdentityModelStore,
private val userSwitcher: UserSwitcher,
private val operationRepo: IOperationRepo,
private val configModel: ConfigModel,
+ private val subscriptionModelStore: SubscriptionModelStore,
private val lock: Any,
) {
fun logout() {
@@ -18,20 +20,24 @@ class LogoutHelper(
return
}
- // Create new device-scoped user (clears external ID)
- userSwitcher.createAndSwitchToNewUser()
+ if (configModel.useIdentityVerification == true) {
+ configModel.pushSubscriptionId?.let { pushSubId ->
+ subscriptionModelStore.get(pushSubId)
+ ?.let { it.isDisabledInternally = true }
+ }
- // Enqueue login operation for the new device-scoped user (no external ID)
- operationRepo.enqueue(
- LoginUserOperation(
- configModel.appId,
- identityModelStore.model.onesignalId,
- null,
- // No external ID for device-scoped user
- ),
- )
+ userSwitcher.createAndSwitchToNewUser(suppressBackendOperation = true)
+ } else {
+ userSwitcher.createAndSwitchToNewUser()
- // TODO: remove JWT Token for all future requests.
+ operationRepo.enqueue(
+ LoginUserOperation(
+ configModel.appId,
+ identityModelStore.model.onesignalId,
+ null,
+ ),
+ )
+ }
}
}
}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/UserManager.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/UserManager.kt
index 328cb9da7d..4ec95c0820 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/UserManager.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/UserManager.kt
@@ -1,5 +1,7 @@
package com.onesignal.user.internal
+import com.onesignal.IUserJwtInvalidatedListener
+import com.onesignal.UserJwtInvalidatedEvent
import com.onesignal.common.IDManager
import com.onesignal.common.JSONUtils
import com.onesignal.common.OneSignalUtils
@@ -43,6 +45,21 @@ internal open class UserManager(
get() = _subscriptionManager.subscriptions
val changeHandlersNotifier = EventProducer()
+ private val jwtInvalidatedNotifier = EventProducer()
+
+ fun addJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) {
+ jwtInvalidatedNotifier.subscribe(listener)
+ }
+
+ fun removeJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) {
+ jwtInvalidatedNotifier.unsubscribe(listener)
+ }
+
+ fun fireJwtInvalidated(externalId: String) {
+ jwtInvalidatedNotifier.fireOnMain {
+ it.onUserJwtInvalidated(UserJwtInvalidatedEvent(externalId))
+ }
+ }
override val pushSubscription: IPushSubscription
get() = _subscriptionManager.subscriptions.push
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/IIdentityBackendService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/IIdentityBackendService.kt
index 4278d8002b..a09f40ca68 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/IIdentityBackendService.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/IIdentityBackendService.kt
@@ -1,6 +1,7 @@
package com.onesignal.user.internal.backend
import com.onesignal.common.exceptions.BackendException
+import com.onesignal.debug.internal.logging.Logging
interface IIdentityBackendService {
/**
@@ -18,6 +19,7 @@ interface IIdentityBackendService {
aliasLabel: String,
aliasValue: String,
identities: Map,
+ jwt: String? = null,
): Map
/**
@@ -35,6 +37,7 @@ interface IIdentityBackendService {
aliasLabel: String,
aliasValue: String,
aliasLabelToDelete: String,
+ jwt: String? = null,
)
}
@@ -48,4 +51,23 @@ object IdentityConstants {
* The alias label for the internal onesignal ID alias.
*/
const val ONESIGNAL_ID = "onesignal_id"
+
+ /**
+ * Resolves which alias (external_id vs onesignal_id) should be used in backend API paths.
+ * When identity verification is enabled and the operation has an externalId, routes through
+ * external_id; otherwise falls back to onesignal_id.
+ */
+ fun resolveAlias(
+ useIdentityVerification: Boolean?,
+ externalId: String?,
+ onesignalId: String,
+ ): Pair {
+ if (useIdentityVerification == true) {
+ if (externalId != null) {
+ return EXTERNAL_ID to externalId
+ }
+ Logging.error("Identity verification is enabled but externalId is null. Falling back to onesignal_id.")
+ }
+ return ONESIGNAL_ID to onesignalId
+ }
}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/ISubscriptionBackendService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/ISubscriptionBackendService.kt
index 7bcf23fdb2..e6e65bff1f 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/ISubscriptionBackendService.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/ISubscriptionBackendService.kt
@@ -22,6 +22,7 @@ interface ISubscriptionBackendService {
aliasLabel: String,
aliasValue: String,
subscription: SubscriptionObject,
+ jwt: String? = null,
): Pair?
/**
@@ -35,6 +36,7 @@ interface ISubscriptionBackendService {
appId: String,
subscriptionId: String,
subscription: SubscriptionObject,
+ jwt: String? = null,
): RywData?
/**
@@ -46,6 +48,7 @@ interface ISubscriptionBackendService {
suspend fun deleteSubscription(
appId: String,
subscriptionId: String,
+ jwt: String? = null,
)
/**
@@ -61,6 +64,7 @@ interface ISubscriptionBackendService {
subscriptionId: String,
aliasLabel: String,
aliasValue: String,
+ jwt: String? = null,
)
/**
@@ -74,5 +78,6 @@ interface ISubscriptionBackendService {
suspend fun getIdentityFromSubscription(
appId: String,
subscriptionId: String,
+ jwt: String? = null,
): Map
}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/IUserBackendService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/IUserBackendService.kt
index 4cec114b5a..b849fc4c42 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/IUserBackendService.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/IUserBackendService.kt
@@ -24,6 +24,7 @@ interface IUserBackendService {
identities: Map,
subscriptions: List,
properties: Map,
+ jwt: String? = null,
): CreateUserResponse
// TODO: Change to send only the push subscription, optimally
@@ -48,6 +49,7 @@ interface IUserBackendService {
properties: PropertiesObject,
refreshDeviceMetadata: Boolean,
propertyiesDelta: PropertiesDeltasObject,
+ jwt: String? = null,
): RywData?
/**
@@ -65,6 +67,7 @@ interface IUserBackendService {
appId: String,
aliasLabel: String,
aliasValue: String,
+ jwt: String? = null,
): CreateUserResponse
}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/IdentityBackendService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/IdentityBackendService.kt
index adfff7bdc9..614b8a3bf3 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/IdentityBackendService.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/IdentityBackendService.kt
@@ -4,6 +4,7 @@ import com.onesignal.common.exceptions.BackendException
import com.onesignal.common.putMap
import com.onesignal.common.toMap
import com.onesignal.core.internal.http.IHttpClient
+import com.onesignal.core.internal.http.impl.OptionalHeaders
import com.onesignal.user.internal.backend.IIdentityBackendService
import org.json.JSONObject
@@ -15,12 +16,13 @@ internal class IdentityBackendService(
aliasLabel: String,
aliasValue: String,
identities: Map,
+ jwt: String?,
): Map {
val requestJSONObject =
JSONObject()
.put("identity", JSONObject().putMap(identities))
- val response = _httpClient.patch("apps/$appId/users/by/$aliasLabel/$aliasValue/identity", requestJSONObject)
+ val response = _httpClient.patch("apps/$appId/users/by/$aliasLabel/$aliasValue/identity", requestJSONObject, jwt?.let { OptionalHeaders(jwt = it) })
if (!response.isSuccess) {
throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds)
@@ -36,8 +38,9 @@ internal class IdentityBackendService(
aliasLabel: String,
aliasValue: String,
aliasLabelToDelete: String,
+ jwt: String?,
) {
- val response = _httpClient.delete("apps/$appId/users/by/$aliasLabel/$aliasValue/identity/$aliasLabelToDelete")
+ val response = _httpClient.delete("apps/$appId/users/by/$aliasLabel/$aliasValue/identity/$aliasLabelToDelete", jwt?.let { OptionalHeaders(jwt = it) })
if (!response.isSuccess) {
throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds)
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/SubscriptionBackendService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/SubscriptionBackendService.kt
index a2266d4d36..1003dd84c5 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/SubscriptionBackendService.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/SubscriptionBackendService.kt
@@ -7,6 +7,7 @@ import com.onesignal.common.safeLong
import com.onesignal.common.safeString
import com.onesignal.common.toMap
import com.onesignal.core.internal.http.IHttpClient
+import com.onesignal.core.internal.http.impl.OptionalHeaders
import com.onesignal.user.internal.backend.ISubscriptionBackendService
import com.onesignal.user.internal.backend.SubscriptionObject
import org.json.JSONObject
@@ -19,11 +20,12 @@ internal class SubscriptionBackendService(
aliasLabel: String,
aliasValue: String,
subscription: SubscriptionObject,
+ jwt: String?,
): Pair? {
val jsonSubscription = JSONConverter.convertToJSON(subscription)
val requestJSON = JSONObject().put("subscription", jsonSubscription)
- val response = _httpClient.post("apps/$appId/users/by/$aliasLabel/$aliasValue/subscriptions", requestJSON)
+ val response = _httpClient.post("apps/$appId/users/by/$aliasLabel/$aliasValue/subscriptions", requestJSON, jwt?.let { OptionalHeaders(jwt = it) })
if (!response.isSuccess) {
throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds)
@@ -50,12 +52,13 @@ internal class SubscriptionBackendService(
appId: String,
subscriptionId: String,
subscription: SubscriptionObject,
+ jwt: String?,
): RywData? {
val requestJSON =
JSONObject()
.put("subscription", JSONConverter.convertToJSON(subscription))
- val response = _httpClient.patch("apps/$appId/subscriptions/$subscriptionId", requestJSON)
+ val response = _httpClient.patch("apps/$appId/subscriptions/$subscriptionId", requestJSON, jwt?.let { OptionalHeaders(jwt = it) })
if (!response.isSuccess) {
throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds)
@@ -76,8 +79,9 @@ internal class SubscriptionBackendService(
override suspend fun deleteSubscription(
appId: String,
subscriptionId: String,
+ jwt: String?,
) {
- val response = _httpClient.delete("apps/$appId/subscriptions/$subscriptionId")
+ val response = _httpClient.delete("apps/$appId/subscriptions/$subscriptionId", jwt?.let { OptionalHeaders(jwt = it) })
if (!response.isSuccess) {
throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds)
@@ -89,12 +93,13 @@ internal class SubscriptionBackendService(
subscriptionId: String,
aliasLabel: String,
aliasValue: String,
+ jwt: String?,
) {
val requestJSON =
JSONObject()
.put("identity", JSONObject().put(aliasLabel, aliasValue))
- val response = _httpClient.patch("apps/$appId/subscriptions/$subscriptionId/owner", requestJSON)
+ val response = _httpClient.patch("apps/$appId/subscriptions/$subscriptionId/owner", requestJSON, jwt?.let { OptionalHeaders(jwt = it) })
if (!response.isSuccess) {
throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds)
@@ -104,8 +109,9 @@ internal class SubscriptionBackendService(
override suspend fun getIdentityFromSubscription(
appId: String,
subscriptionId: String,
+ jwt: String?,
): Map {
- val response = _httpClient.get("apps/$appId/subscriptions/$subscriptionId/user/identity")
+ val response = _httpClient.get("apps/$appId/subscriptions/$subscriptionId/user/identity", jwt?.let { OptionalHeaders(jwt = it) })
if (!response.isSuccess) {
throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds)
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/UserBackendService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/UserBackendService.kt
index 1a1514018f..8a5c58d691 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/UserBackendService.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/UserBackendService.kt
@@ -6,6 +6,7 @@ import com.onesignal.common.putMap
import com.onesignal.common.safeLong
import com.onesignal.common.safeString
import com.onesignal.core.internal.http.IHttpClient
+import com.onesignal.core.internal.http.impl.OptionalHeaders
import com.onesignal.user.internal.backend.CreateUserResponse
import com.onesignal.user.internal.backend.IUserBackendService
import com.onesignal.user.internal.backend.PropertiesDeltasObject
@@ -21,6 +22,7 @@ internal class UserBackendService(
identities: Map,
subscriptions: List,
properties: Map,
+ jwt: String?,
): CreateUserResponse {
val requestJSON = JSONObject()
@@ -39,7 +41,7 @@ internal class UserBackendService(
requestJSON.put("refresh_device_metadata", true)
- val response = _httpClient.post("apps/$appId/users", requestJSON)
+ val response = _httpClient.post("apps/$appId/users", requestJSON, jwt?.let { OptionalHeaders(jwt = it) })
if (!response.isSuccess) {
throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds)
@@ -55,6 +57,7 @@ internal class UserBackendService(
properties: PropertiesObject,
refreshDeviceMetadata: Boolean,
propertyiesDelta: PropertiesDeltasObject,
+ jwt: String?,
): RywData? {
val jsonObject =
JSONObject()
@@ -68,7 +71,7 @@ internal class UserBackendService(
jsonObject.put("deltas", JSONConverter.convertToJSON(propertyiesDelta))
}
- val response = _httpClient.patch("apps/$appId/users/by/$aliasLabel/$aliasValue", jsonObject)
+ val response = _httpClient.patch("apps/$appId/users/by/$aliasLabel/$aliasValue", jsonObject, jwt?.let { OptionalHeaders(jwt = it) })
if (!response.isSuccess) {
throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds)
@@ -90,8 +93,9 @@ internal class UserBackendService(
appId: String,
aliasLabel: String,
aliasValue: String,
+ jwt: String?,
): CreateUserResponse {
- val response = _httpClient.get("apps/$appId/users/by/$aliasLabel/$aliasValue")
+ val response = _httpClient.get("apps/$appId/users/by/$aliasLabel/$aliasValue", jwt?.let { OptionalHeaders(jwt = it) })
if (!response.isSuccess) {
throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds)
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/customEvents/ICustomEventBackendService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/customEvents/ICustomEventBackendService.kt
index 92474635ab..8c624f1f76 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/customEvents/ICustomEventBackendService.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/customEvents/ICustomEventBackendService.kt
@@ -20,5 +20,6 @@ interface ICustomEventBackendService {
eventName: String,
eventProperties: String?,
metadata: CustomEventMetadata,
+ jwt: String? = null,
): ExecutionResponse
}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/customEvents/impl/CustomEventBackendService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/customEvents/impl/CustomEventBackendService.kt
index 096fa67456..eccd67b650 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/customEvents/impl/CustomEventBackendService.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/customEvents/impl/CustomEventBackendService.kt
@@ -3,6 +3,7 @@ package com.onesignal.user.internal.customEvents.impl
import com.onesignal.common.DateUtils
import com.onesignal.common.exceptions.BackendException
import com.onesignal.core.internal.http.IHttpClient
+import com.onesignal.core.internal.http.impl.OptionalHeaders
import com.onesignal.core.internal.operations.ExecutionResponse
import com.onesignal.core.internal.operations.ExecutionResult
import com.onesignal.user.internal.customEvents.ICustomEventBackendService
@@ -21,6 +22,7 @@ internal class CustomEventBackendService(
eventName: String,
eventProperties: String?,
metadata: CustomEventMetadata,
+ jwt: String?,
): ExecutionResponse {
val body = JSONObject()
body.put("name", eventName)
@@ -42,7 +44,7 @@ internal class CustomEventBackendService(
body.put("payload", payload)
val jsonObject = JSONObject().put("events", JSONArray().put(body))
- val response = httpClient.post("apps/$appId/custom_events", jsonObject)
+ val response = httpClient.post("apps/$appId/custom_events", jsonObject, jwt?.let { OptionalHeaders(jwt = it) })
if (!response.isSuccess) {
throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds)
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/identity/JwtTokenStore.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/identity/JwtTokenStore.kt
new file mode 100644
index 0000000000..2bbda35723
--- /dev/null
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/identity/JwtTokenStore.kt
@@ -0,0 +1,105 @@
+package com.onesignal.user.internal.identity
+
+import com.onesignal.core.internal.preferences.IPreferencesService
+import com.onesignal.core.internal.preferences.PreferenceOneSignalKeys
+import com.onesignal.core.internal.preferences.PreferenceStores
+import com.onesignal.debug.internal.logging.Logging
+import org.json.JSONObject
+
+/**
+ * Persistent store mapping externalId -> JWT token. Supports multiple users simultaneously
+ * so that queued operations for a previous user can still resolve their JWT at execution time.
+ *
+ * Storage is unconditional (callers store JWTs regardless of the identity-verification flag).
+ * Only *usage* of JWTs (Authorization header, gating, alias resolution) is gated on
+ * [com.onesignal.core.internal.config.ConfigModel.useIdentityVerification].
+ */
+class JwtTokenStore(
+ private val _prefs: IPreferencesService,
+) {
+ private val tokens: MutableMap = mutableMapOf()
+ private var isLoaded = false
+
+ /** Not thread-safe; callers must hold `synchronized(tokens)`. */
+ private fun ensureLoaded() {
+ if (isLoaded) return
+ val json =
+ _prefs.getString(
+ PreferenceStores.ONESIGNAL,
+ PreferenceOneSignalKeys.PREFS_OS_JWT_TOKENS,
+ )
+ if (json != null) {
+ try {
+ val obj = JSONObject(json)
+ for (key in obj.keys()) {
+ tokens[key] = obj.getString(key)
+ }
+ } catch (e: Exception) {
+ Logging.warn("JwtTokenStore: failed to parse persisted tokens, starting fresh", e)
+ }
+ }
+ isLoaded = true
+ }
+
+ /** Not thread-safe; callers must hold `synchronized(tokens)`. */
+ private fun persist() {
+ _prefs.saveString(
+ PreferenceStores.ONESIGNAL,
+ PreferenceOneSignalKeys.PREFS_OS_JWT_TOKENS,
+ JSONObject(tokens.toMap()).toString(),
+ )
+ }
+
+ /**
+ * Returns the JWT for the given [externalId], or null if none is stored.
+ */
+ fun getJwt(externalId: String): String? {
+ synchronized(tokens) {
+ ensureLoaded()
+ return tokens[externalId]
+ }
+ }
+
+ /**
+ * Stores (or replaces) the JWT for [externalId]. Passing a null [jwt] is a no-op;
+ * use [invalidateJwt] to remove a token.
+ */
+ fun putJwt(
+ externalId: String,
+ jwt: String?,
+ ) {
+ if (jwt == null) return
+ synchronized(tokens) {
+ ensureLoaded()
+ tokens[externalId] = jwt
+ persist()
+ }
+ }
+
+ /**
+ * Removes the JWT for [externalId], marking it as invalid. Operations for this user
+ * will be held until a new JWT is provided via [putJwt].
+ */
+ fun invalidateJwt(externalId: String) {
+ synchronized(tokens) {
+ ensureLoaded()
+ if (tokens.remove(externalId) != null) {
+ persist()
+ }
+ }
+ }
+
+ /**
+ * Removes all stored JWTs whose externalId is NOT in [activeIds].
+ * Called on cold start after loading persisted operations to prevent unbounded growth.
+ */
+ fun pruneToExternalIds(activeIds: Set) {
+ synchronized(tokens) {
+ ensureLoaded()
+ val removed = tokens.keys.retainAll(activeIds)
+ if (removed) {
+ persist()
+ }
+ }
+ }
+}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/LoginUserOperation.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/LoginUserOperation.kt
index b283cc3da0..9164ab39ca 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/LoginUserOperation.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/LoginUserOperation.kt
@@ -32,15 +32,6 @@ class LoginUserOperation() : Operation(LoginUserOperationExecutor.LOGIN_USER) {
setStringProperty(::onesignalId.name, value)
}
- /**
- * The optional external ID of this newly logged-in user. Must be unique for the [appId].
- */
- var externalId: String?
- get() = getOptStringProperty(::externalId.name)
- private set(value) {
- setOptStringProperty(::externalId.name, value)
- }
-
/**
* The user ID of an existing user the [externalId] will be attempted to be associated to first.
* When null (or non-null but unsuccessful), a new user will be upserted. This ID *may* be locally generated
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/TrackCustomEventOperation.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/TrackCustomEventOperation.kt
index b510a4fd3f..04956e1877 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/TrackCustomEventOperation.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/TrackCustomEventOperation.kt
@@ -30,15 +30,6 @@ class TrackCustomEventOperation() : Operation(CustomEventOperationExecutor.CUSTO
setStringProperty(::onesignalId.name, value)
}
- /**
- * The optional external ID of current logged-in user. Must be unique for the [appId].
- */
- var externalId: String?
- get() = getOptStringProperty(::externalId.name)
- private set(value) {
- setOptStringProperty(::externalId.name, value)
- }
-
/**
* The timestamp when the custom event was created.
*/
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/UpdateSubscriptionOperation.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/UpdateSubscriptionOperation.kt
index 57f17a29f5..426da703dd 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/UpdateSubscriptionOperation.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/UpdateSubscriptionOperation.kt
@@ -86,6 +86,7 @@ class UpdateSubscriptionOperation() : Operation(SubscriptionOperationExecutor.UP
override val groupComparisonType: GroupComparisonType = GroupComparisonType.ALTER
override val canStartExecute: Boolean get() = !IDManager.isLocalId(onesignalId) && !IDManager.isLocalId(subscriptionId)
override val applyToRecordId: String get() = subscriptionId
+ override val requiresJwt: Boolean get() = false
constructor(appId: String, onesignalId: String, subscriptionId: String, type: SubscriptionType, enabled: Boolean, address: String, status: SubscriptionStatus) : this() {
this.appId = appId
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/CustomEventOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/CustomEventOperationExecutor.kt
index 2e1046e6c6..14166713df 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/CustomEventOperationExecutor.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/CustomEventOperationExecutor.kt
@@ -13,12 +13,14 @@ import com.onesignal.core.internal.operations.IOperationExecutor
import com.onesignal.core.internal.operations.Operation
import com.onesignal.user.internal.customEvents.ICustomEventBackendService
import com.onesignal.user.internal.customEvents.impl.CustomEventMetadata
+import com.onesignal.user.internal.identity.JwtTokenStore
import com.onesignal.user.internal.operations.TrackCustomEventOperation
internal class CustomEventOperationExecutor(
private val customEventBackendService: ICustomEventBackendService,
private val applicationService: IApplicationService,
private val deviceService: IDeviceService,
+ private val _jwtTokenStore: JwtTokenStore,
) : IOperationExecutor {
override val operations: List
get() = listOf(CUSTOM_EVENT)
@@ -40,6 +42,7 @@ internal class CustomEventOperationExecutor(
try {
when (operation) {
is TrackCustomEventOperation -> {
+ val jwt = operation.externalId?.let { _jwtTokenStore.getJwt(it) }
customEventBackendService.sendCustomEvent(
operation.appId,
operation.onesignalId,
@@ -48,6 +51,7 @@ internal class CustomEventOperationExecutor(
operation.eventName,
operation.eventProperties,
eventMetadataJson,
+ jwt,
)
}
}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/IdentityOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/IdentityOperationExecutor.kt
index 104fe9569f..f12831c3f9 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/IdentityOperationExecutor.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/IdentityOperationExecutor.kt
@@ -3,6 +3,7 @@ package com.onesignal.user.internal.operations.impl.executors
import com.onesignal.common.NetworkUtils
import com.onesignal.common.exceptions.BackendException
import com.onesignal.common.modeling.ModelChangeTags
+import com.onesignal.core.internal.config.ConfigModelStore
import com.onesignal.core.internal.operations.ExecutionResponse
import com.onesignal.core.internal.operations.ExecutionResult
import com.onesignal.core.internal.operations.IOperationExecutor
@@ -12,6 +13,7 @@ import com.onesignal.user.internal.backend.IIdentityBackendService
import com.onesignal.user.internal.backend.IdentityConstants
import com.onesignal.user.internal.builduser.IRebuildUserService
import com.onesignal.user.internal.identity.IdentityModelStore
+import com.onesignal.user.internal.identity.JwtTokenStore
import com.onesignal.user.internal.operations.DeleteAliasOperation
import com.onesignal.user.internal.operations.SetAliasOperation
import com.onesignal.user.internal.operations.impl.states.NewRecordsState
@@ -21,6 +23,8 @@ internal class IdentityOperationExecutor(
private val _identityModelStore: IdentityModelStore,
private val _buildUserService: IRebuildUserService,
private val _newRecordState: NewRecordsState,
+ private val _configModelStore: ConfigModelStore,
+ private val _jwtTokenStore: JwtTokenStore,
) : IOperationExecutor {
override val operations: List
get() = listOf(SET_ALIAS, DELETE_ALIAS)
@@ -44,12 +48,21 @@ internal class IdentityOperationExecutor(
val lastOperation = operations.last()
if (lastOperation is SetAliasOperation) {
+ val (aliasLabel, aliasValue) =
+ IdentityConstants.resolveAlias(
+ _configModelStore.model.useIdentityVerification,
+ lastOperation.externalId,
+ lastOperation.onesignalId,
+ )
+ val jwt = lastOperation.externalId?.let { _jwtTokenStore.getJwt(it) }
+
try {
_identityBackend.setAlias(
lastOperation.appId,
- IdentityConstants.ONESIGNAL_ID,
- lastOperation.onesignalId,
+ aliasLabel,
+ aliasValue,
mapOf(lastOperation.label to lastOperation.value),
+ jwt,
)
// ensure the now created alias is in the model as long as the user is still current.
@@ -87,12 +100,21 @@ internal class IdentityOperationExecutor(
}
}
} else if (lastOperation is DeleteAliasOperation) {
+ val (aliasLabel, aliasValue) =
+ IdentityConstants.resolveAlias(
+ _configModelStore.model.useIdentityVerification,
+ lastOperation.externalId,
+ lastOperation.onesignalId,
+ )
+ val jwt = lastOperation.externalId?.let { _jwtTokenStore.getJwt(it) }
+
try {
_identityBackend.deleteAlias(
lastOperation.appId,
- IdentityConstants.ONESIGNAL_ID,
- lastOperation.onesignalId,
+ aliasLabel,
+ aliasValue,
lastOperation.label,
+ jwt,
)
// ensure the now deleted alias is not in the model as long as the user is still current.
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserFromSubscriptionOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserFromSubscriptionOperationExecutor.kt
index 84093eeccb..cf63ab2e20 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserFromSubscriptionOperationExecutor.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserFromSubscriptionOperationExecutor.kt
@@ -3,6 +3,7 @@ package com.onesignal.user.internal.operations.impl.executors
import com.onesignal.common.NetworkUtils
import com.onesignal.common.exceptions.BackendException
import com.onesignal.common.modeling.ModelChangeTags
+import com.onesignal.core.internal.config.ConfigModelStore
import com.onesignal.core.internal.operations.ExecutionResponse
import com.onesignal.core.internal.operations.ExecutionResult
import com.onesignal.core.internal.operations.IOperationExecutor
@@ -20,6 +21,7 @@ internal class LoginUserFromSubscriptionOperationExecutor(
private val _subscriptionBackend: ISubscriptionBackendService,
private val _identityModelStore: IdentityModelStore,
private val _propertiesModelStore: PropertiesModelStore,
+ private val _configModelStore: ConfigModelStore,
) : IOperationExecutor {
override val operations: List
get() = listOf(LOGIN_USER_FROM_SUBSCRIPTION_USER)
@@ -27,6 +29,11 @@ internal class LoginUserFromSubscriptionOperationExecutor(
override suspend fun execute(operations: List): ExecutionResponse {
Logging.debug("LoginUserFromSubscriptionOperationExecutor(operation: $operations)")
+ if (_configModelStore.model.useIdentityVerification == true) {
+ Logging.warn("LoginUserFromSubscriptionOperation is not supported when identity verification is enabled. Dropping.")
+ return ExecutionResponse(ExecutionResult.FAIL_NORETRY)
+ }
+
if (operations.size > 1) {
throw Exception("Only supports one operation! Attempted operations:\n$operations")
}
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt
index 46968b3e71..c80adff4ae 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt
@@ -24,6 +24,7 @@ import com.onesignal.user.internal.backend.IdentityConstants
import com.onesignal.user.internal.backend.SubscriptionObject
import com.onesignal.user.internal.backend.SubscriptionObjectType
import com.onesignal.user.internal.identity.IdentityModelStore
+import com.onesignal.user.internal.identity.JwtTokenStore
import com.onesignal.user.internal.operations.CreateSubscriptionOperation
import com.onesignal.user.internal.operations.DeleteSubscriptionOperation
import com.onesignal.user.internal.operations.LoginUserOperation
@@ -47,6 +48,7 @@ internal class LoginUserOperationExecutor(
private val _subscriptionsModelStore: SubscriptionModelStore,
private val _configModelStore: ConfigModelStore,
private val _languageContext: ILanguageContext,
+ private val _jwtTokenStore: JwtTokenStore,
) : IOperationExecutor {
override val operations: List
get() = listOf(LOGIN_USER)
@@ -168,7 +170,8 @@ internal class LoginUserOperationExecutor(
try {
val subscriptionList = subscriptions.toList()
- val response = _userBackend.createUser(createUserOperation.appId, identities, subscriptionList.map { it.second }, properties)
+ val jwt = createUserOperation.externalId?.let { _jwtTokenStore.getJwt(it) }
+ val response = _userBackend.createUser(createUserOperation.appId, identities, subscriptionList.map { it.second }, properties, jwt)
val idTranslations = mutableMapOf()
// Add the "local-to-backend" ID translation to the IdentifierTranslator for any operations that were
// *not* executed but still reference the locally-generated IDs.
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/RefreshUserOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/RefreshUserOperationExecutor.kt
index d7bfa0f671..02e10bbc22 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/RefreshUserOperationExecutor.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/RefreshUserOperationExecutor.kt
@@ -17,6 +17,7 @@ import com.onesignal.user.internal.backend.SubscriptionObjectType
import com.onesignal.user.internal.builduser.IRebuildUserService
import com.onesignal.user.internal.identity.IdentityModel
import com.onesignal.user.internal.identity.IdentityModelStore
+import com.onesignal.user.internal.identity.JwtTokenStore
import com.onesignal.user.internal.operations.RefreshUserOperation
import com.onesignal.user.internal.operations.impl.states.NewRecordsState
import com.onesignal.user.internal.properties.PropertiesModel
@@ -34,6 +35,7 @@ internal class RefreshUserOperationExecutor(
private val _configModelStore: ConfigModelStore,
private val _buildUserService: IRebuildUserService,
private val _newRecordState: NewRecordsState,
+ private val _jwtTokenStore: JwtTokenStore,
) : IOperationExecutor {
override val operations: List
get() = listOf(REFRESH_USER)
@@ -54,12 +56,21 @@ internal class RefreshUserOperationExecutor(
}
private suspend fun getUser(op: RefreshUserOperation): ExecutionResponse {
+ val (aliasLabel, aliasValue) =
+ IdentityConstants.resolveAlias(
+ _configModelStore.model.useIdentityVerification,
+ op.externalId,
+ op.onesignalId,
+ )
+ val jwt = op.externalId?.let { _jwtTokenStore.getJwt(it) }
+
try {
val response =
_userBackend.getUser(
op.appId,
- IdentityConstants.ONESIGNAL_ID,
- op.onesignalId,
+ aliasLabel,
+ aliasValue,
+ jwt,
)
if (op.onesignalId != _identityModelStore.model.onesignalId) {
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt
index 81ab0bb687..97d78ec4d7 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt
@@ -26,6 +26,7 @@ import com.onesignal.user.internal.backend.IdentityConstants
import com.onesignal.user.internal.backend.SubscriptionObject
import com.onesignal.user.internal.backend.SubscriptionObjectType
import com.onesignal.user.internal.builduser.IRebuildUserService
+import com.onesignal.user.internal.identity.JwtTokenStore
import com.onesignal.user.internal.operations.CreateSubscriptionOperation
import com.onesignal.user.internal.operations.DeleteSubscriptionOperation
import com.onesignal.user.internal.operations.TransferSubscriptionOperation
@@ -44,6 +45,7 @@ internal class SubscriptionOperationExecutor(
private val _buildUserService: IRebuildUserService,
private val _newRecordState: NewRecordsState,
private val _consistencyManager: IConsistencyManager,
+ private val _jwtTokenStore: JwtTokenStore,
) : IOperationExecutor {
override val operations: List
get() = listOf(CREATE_SUBSCRIPTION, UPDATE_SUBSCRIPTION, DELETE_SUBSCRIPTION, TRANSFER_SUBSCRIPTION)
@@ -107,12 +109,21 @@ internal class SubscriptionOperationExecutor(
AndroidUtils.getAppVersion(_applicationService.appContext),
)
+ val (aliasLabel, aliasValue) =
+ IdentityConstants.resolveAlias(
+ _configModelStore.model.useIdentityVerification,
+ createOperation.externalId,
+ createOperation.onesignalId,
+ )
+ val jwt = createOperation.externalId?.let { _jwtTokenStore.getJwt(it) }
+
val result =
_subscriptionBackend.createSubscription(
createOperation.appId,
- IdentityConstants.ONESIGNAL_ID,
- createOperation.onesignalId,
+ aliasLabel,
+ aliasValue,
subscription,
+ jwt,
) ?: return ExecutionResponse(ExecutionResult.SUCCESS)
val backendSubscriptionId = result.first
@@ -190,7 +201,8 @@ internal class SubscriptionOperationExecutor(
AndroidUtils.getAppVersion(_applicationService.appContext),
)
- val rywData = _subscriptionBackend.updateSubscription(lastOperation.appId, lastOperation.subscriptionId, subscription)
+ val jwt = lastOperation.externalId?.let { _jwtTokenStore.getJwt(it) }
+ val rywData = _subscriptionBackend.updateSubscription(lastOperation.appId, lastOperation.subscriptionId, subscription, jwt)
if (rywData != null) {
_consistencyManager.setRywData(startingOperation.onesignalId, IamFetchRywTokenKey.SUBSCRIPTION, rywData)
@@ -239,12 +251,21 @@ internal class SubscriptionOperationExecutor(
// TODO: whenever the end-user changes users, we need to add the read-your-write token here, currently no code to handle the re-fetch IAMs
private suspend fun transferSubscription(startingOperation: TransferSubscriptionOperation): ExecutionResponse {
+ val (aliasLabel, aliasValue) =
+ IdentityConstants.resolveAlias(
+ _configModelStore.model.useIdentityVerification,
+ startingOperation.externalId,
+ startingOperation.onesignalId,
+ )
+ val jwt = startingOperation.externalId?.let { _jwtTokenStore.getJwt(it) }
+
try {
_subscriptionBackend.transferSubscription(
startingOperation.appId,
startingOperation.subscriptionId,
- IdentityConstants.ONESIGNAL_ID,
- startingOperation.onesignalId,
+ aliasLabel,
+ aliasValue,
+ jwt,
)
} catch (ex: BackendException) {
val responseType = NetworkUtils.getResponseStatusType(ex.statusCode)
@@ -275,8 +296,10 @@ internal class SubscriptionOperationExecutor(
}
private suspend fun deleteSubscription(op: DeleteSubscriptionOperation): ExecutionResponse {
+ val jwt = op.externalId?.let { _jwtTokenStore.getJwt(it) }
+
try {
- _subscriptionBackend.deleteSubscription(op.appId, op.subscriptionId)
+ _subscriptionBackend.deleteSubscription(op.appId, op.subscriptionId, jwt)
// remove the subscription model as a HYDRATE in case for some reason it still exists.
_subscriptionModelStore.remove(op.subscriptionId, ModelChangeTags.HYDRATE)
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/UpdateUserOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/UpdateUserOperationExecutor.kt
index e529035ec1..090b3f3904 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/UpdateUserOperationExecutor.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/UpdateUserOperationExecutor.kt
@@ -6,6 +6,7 @@ import com.onesignal.common.consistency.enums.IamFetchRywTokenKey
import com.onesignal.common.consistency.models.IConsistencyManager
import com.onesignal.common.exceptions.BackendException
import com.onesignal.common.modeling.ModelChangeTags
+import com.onesignal.core.internal.config.ConfigModelStore
import com.onesignal.core.internal.operations.ExecutionResponse
import com.onesignal.core.internal.operations.ExecutionResult
import com.onesignal.core.internal.operations.IOperationExecutor
@@ -19,6 +20,7 @@ import com.onesignal.user.internal.backend.PropertiesObject
import com.onesignal.user.internal.backend.PurchaseObject
import com.onesignal.user.internal.builduser.IRebuildUserService
import com.onesignal.user.internal.identity.IdentityModelStore
+import com.onesignal.user.internal.identity.JwtTokenStore
import com.onesignal.user.internal.operations.DeleteTagOperation
import com.onesignal.user.internal.operations.SetPropertyOperation
import com.onesignal.user.internal.operations.SetTagOperation
@@ -35,6 +37,8 @@ internal class UpdateUserOperationExecutor(
private val _buildUserService: IRebuildUserService,
private val _newRecordState: NewRecordsState,
private val _consistencyManager: IConsistencyManager,
+ private val _configModelStore: ConfigModelStore,
+ private val _jwtTokenStore: JwtTokenStore,
) : IOperationExecutor {
override val operations: List
get() = listOf(SET_TAG, DELETE_TAG, SET_PROPERTY, TRACK_SESSION_START, TRACK_SESSION_END, TRACK_PURCHASE)
@@ -137,15 +141,25 @@ internal class UpdateUserOperationExecutor(
}
if (appId != null && onesignalId != null) {
+ val firstOp = operations.first()
+ val (aliasLabel, aliasValue) =
+ IdentityConstants.resolveAlias(
+ _configModelStore.model.useIdentityVerification,
+ firstOp.externalId,
+ onesignalId,
+ )
+ val jwt = firstOp.externalId?.let { _jwtTokenStore.getJwt(it) }
+
try {
val rywData =
_userBackend.updateUser(
appId,
- IdentityConstants.ONESIGNAL_ID,
- onesignalId,
+ aliasLabel,
+ aliasValue,
propertiesObject,
refreshDeviceMetadata,
deltasObject,
+ jwt,
)
if (rywData != null) {
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/listeners/IdentityModelStoreListener.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/listeners/IdentityModelStoreListener.kt
index 90a565a5a2..b34d7069b7 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/listeners/IdentityModelStoreListener.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/listeners/IdentityModelStoreListener.kt
@@ -10,10 +10,14 @@ import com.onesignal.user.internal.operations.DeleteAliasOperation
import com.onesignal.user.internal.operations.SetAliasOperation
internal class IdentityModelStoreListener(
- store: IdentityModelStore,
+ private val _identityModelStore: IdentityModelStore,
opRepo: IOperationRepo,
private val _configModelStore: ConfigModelStore,
-) : SingletonModelStoreListener(store, opRepo) {
+) : SingletonModelStoreListener(_identityModelStore, opRepo) {
+ private fun shouldSuppressForAnonymousUser(): Boolean =
+ _configModelStore.model.useIdentityVerification == true &&
+ _identityModelStore.model.externalId == null
+
override fun getReplaceOperation(model: IdentityModel): Operation? {
// when the identity model is replaced, nothing to do on the backend. Already handled via login process.
return null
@@ -25,7 +29,9 @@ internal class IdentityModelStoreListener(
property: String,
oldValue: Any?,
newValue: Any?,
- ): Operation {
+ ): Operation? {
+ if (shouldSuppressForAnonymousUser()) return null
+
return if (newValue != null && newValue is String) {
SetAliasOperation(_configModelStore.model.appId, model.onesignalId, property, newValue)
} else {
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/listeners/PropertiesModelStoreListener.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/listeners/PropertiesModelStoreListener.kt
index d020c5cc66..8ca4d7326a 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/listeners/PropertiesModelStoreListener.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/listeners/PropertiesModelStoreListener.kt
@@ -4,6 +4,7 @@ import com.onesignal.core.internal.config.ConfigModelStore
import com.onesignal.core.internal.operations.IOperationRepo
import com.onesignal.core.internal.operations.Operation
import com.onesignal.core.internal.operations.listeners.SingletonModelStoreListener
+import com.onesignal.user.internal.identity.IdentityModelStore
import com.onesignal.user.internal.operations.DeleteTagOperation
import com.onesignal.user.internal.operations.SetPropertyOperation
import com.onesignal.user.internal.operations.SetTagOperation
@@ -14,7 +15,12 @@ internal class PropertiesModelStoreListener(
store: PropertiesModelStore,
opRepo: IOperationRepo,
private val _configModelStore: ConfigModelStore,
+ private val _identityModelStore: IdentityModelStore,
) : SingletonModelStoreListener(store, opRepo) {
+ private fun shouldSuppressForAnonymousUser(): Boolean =
+ _configModelStore.model.useIdentityVerification == true &&
+ _identityModelStore.model.externalId == null
+
override fun getReplaceOperation(model: PropertiesModel): Operation? {
// when the property model is replaced, nothing to do on the backend. Already handled via login process.
return null
@@ -27,6 +33,8 @@ internal class PropertiesModelStoreListener(
oldValue: Any?,
newValue: Any?,
): Operation? {
+ if (shouldSuppressForAnonymousUser()) return null
+
// for any of the property changes, we do not need to fire an operation.
if (path.startsWith(PropertiesModel::locationTimestamp.name) ||
path.startsWith(PropertiesModel::locationBackground.name) ||
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/listeners/SubscriptionModelStoreListener.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/listeners/SubscriptionModelStoreListener.kt
index f0002940e9..4be3aa2e31 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/listeners/SubscriptionModelStoreListener.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/listeners/SubscriptionModelStoreListener.kt
@@ -18,7 +18,13 @@ internal class SubscriptionModelStoreListener(
private val _identityModelStore: IdentityModelStore,
private val _configModelStore: ConfigModelStore,
) : ModelStoreListener(store, opRepo) {
- override fun getAddOperation(model: SubscriptionModel): Operation {
+ private fun shouldSuppressForAnonymousUser(): Boolean =
+ _configModelStore.model.useIdentityVerification == true &&
+ _identityModelStore.model.externalId == null
+
+ override fun getAddOperation(model: SubscriptionModel): Operation? {
+ if (shouldSuppressForAnonymousUser()) return null
+
val enabledAndStatus = getSubscriptionEnabledAndStatus(model)
return CreateSubscriptionOperation(
_configModelStore.model.appId,
@@ -31,7 +37,9 @@ internal class SubscriptionModelStoreListener(
)
}
- override fun getRemoveOperation(model: SubscriptionModel): Operation {
+ override fun getRemoveOperation(model: SubscriptionModel): Operation? {
+ if (shouldSuppressForAnonymousUser()) return null
+
return DeleteSubscriptionOperation(_configModelStore.model.appId, _identityModelStore.model.onesignalId, model.id)
}
@@ -41,7 +49,9 @@ internal class SubscriptionModelStoreListener(
property: String,
oldValue: Any?,
newValue: Any?,
- ): Operation {
+ ): Operation? {
+ if (shouldSuppressForAnonymousUser()) return null
+
val enabledAndStatus = getSubscriptionEnabledAndStatus(model)
return UpdateSubscriptionOperation(
_configModelStore.model.appId,
@@ -56,6 +66,10 @@ internal class SubscriptionModelStoreListener(
companion object {
fun getSubscriptionEnabledAndStatus(model: SubscriptionModel): Pair {
+ if (model.isDisabledInternally) {
+ return Pair(false, SubscriptionStatus.UNSUBSCRIBE)
+ }
+
val status: SubscriptionStatus
val enabled: Boolean
diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/subscriptions/SubscriptionModel.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/subscriptions/SubscriptionModel.kt
index c7bde3aae8..a4622d3aee 100644
--- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/subscriptions/SubscriptionModel.kt
+++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/subscriptions/SubscriptionModel.kt
@@ -92,6 +92,20 @@ class SubscriptionModel : Model() {
setBooleanProperty(::optedIn.name, value)
}
+ /**
+ * Set to true by the SDK when logout is called with Identity Verification enabled.
+ * The real [optedIn] and [status] remain unchanged to preserve the user's preference.
+ * When a subscription update is built, this flag causes enabled=false and
+ * status=UNSUBSCRIBE to be sent to the backend instead of the real values.
+ * On the next login, [UserSwitcher.createAndSwitchToNewUser] creates a fresh model
+ * that does not carry this flag (defaults to false), restoring the real state.
+ */
+ var isDisabledInternally: Boolean
+ get() = getBooleanProperty(::isDisabledInternally.name) { false }
+ set(value) {
+ setBooleanProperty(::isDisabledInternally.name, value)
+ }
+
var type: SubscriptionType
get() = getEnumProperty(::type.name)
set(value) {
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/operations/OperationRepoTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/operations/OperationRepoTests.kt
index 164949612c..7fae5773af 100644
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/operations/OperationRepoTests.kt
+++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/operations/OperationRepoTests.kt
@@ -12,6 +12,7 @@ import com.onesignal.debug.LogLevel
import com.onesignal.debug.internal.logging.Logging
import com.onesignal.mocks.MockHelper
import com.onesignal.mocks.MockPreferencesService
+import com.onesignal.user.internal.identity.JwtTokenStore
import com.onesignal.user.internal.operations.ExecutorMocks.Companion.getNewRecordState
import com.onesignal.user.internal.operations.LoginUserOperation
import io.kotest.core.spec.style.FunSpec
@@ -72,6 +73,8 @@ private class Mocks {
configModelStore,
Time(),
getNewRecordState(configModelStore),
+ mockk(relaxed = true),
+ MockHelper.identityModelStore(),
),
recordPrivateCalls = true,
)
@@ -97,6 +100,8 @@ class OperationRepoTests : FunSpec({
mocks.configModelStore,
Time(),
getNewRecordState(mocks.configModelStore),
+ mockk(relaxed = true),
+ MockHelper.identityModelStore(),
),
)
@@ -889,6 +894,152 @@ class OperationRepoTests : FunSpec({
// Verify that the grouped execution happened with both operations
// We can't easily verify the exact list content with MockK, but we verified it in the execution order tracking
}
+
+ test("FAIL_UNAUTHORIZED invalidates JWT and fires handler for identified user") {
+ // Given
+ val configModelStore =
+ MockHelper.configModelStore {
+ it.useIdentityVerification = true
+ }
+ val identityModelStore =
+ MockHelper.identityModelStore {
+ it.externalId = "test-user"
+ }
+ val jwtTokenStore = mockk(relaxed = true)
+ every { jwtTokenStore.getJwt("test-user") } returns "valid-jwt"
+
+ val operationModelStore =
+ run {
+ val operationStoreList = mutableListOf()
+ val mock = mockk()
+ every { mock.loadOperations() } just runs
+ every { mock.list() } answers { operationStoreList.toList() }
+ every { mock.add(any()) } answers { operationStoreList.add(firstArg()) }
+ every { mock.remove(any()) } answers {
+ val id = firstArg()
+ operationStoreList.removeIf { it.id == id }
+ }
+ mock
+ }
+
+ val executor = mockk()
+ every { executor.operations } returns listOf("DUMMY_OPERATION")
+ coEvery { executor.execute(any()) } returns
+ ExecutionResponse(ExecutionResult.FAIL_UNAUTHORIZED) andThen
+ ExecutionResponse(ExecutionResult.SUCCESS)
+
+ val operationRepo =
+ spyk(
+ OperationRepo(
+ listOf(executor),
+ operationModelStore,
+ configModelStore,
+ Time(),
+ getNewRecordState(configModelStore),
+ jwtTokenStore,
+ identityModelStore,
+ ),
+ recordPrivateCalls = true,
+ )
+
+ var handlerCalledWith: String? = null
+ operationRepo.setJwtInvalidatedHandler { externalId ->
+ handlerCalledWith = externalId
+ }
+
+ val operation = mockOperation()
+ every { operation.externalId } returns "test-user"
+
+ // When
+ operationRepo.start()
+ val response = operationRepo.enqueueAndWait(operation)
+
+ // Then
+ response shouldBe true
+ verify { jwtTokenStore.invalidateJwt("test-user") }
+ handlerCalledWith shouldBe "test-user"
+ }
+
+ test("enqueue stamps externalId synchronously before async dispatch") {
+ // Verifies the fix for a race condition where createAndSwitchToNewUser()
+ // could clear the identity model's externalId before the async internalEnqueue
+ // had a chance to stamp it.
+
+ // Given
+ val identityModel = com.onesignal.user.internal.identity.IdentityModel()
+ identityModel.id = "-singleton"
+ identityModel.onesignalId = "onesignal-id"
+ identityModel.externalId = "old-user"
+
+ val identityModelStore = mockk(relaxed = true)
+ every { identityModelStore.model } returns identityModel
+
+ val configModelStore =
+ MockHelper.configModelStore {
+ it.useIdentityVerification = true
+ }
+ val jwtTokenStore = mockk(relaxed = true)
+ every { jwtTokenStore.getJwt("old-user") } returns "valid-jwt"
+
+ val operationModelStore =
+ run {
+ val operationStoreList = mutableListOf()
+ val mock = mockk()
+ every { mock.loadOperations() } just runs
+ every { mock.list() } answers { operationStoreList.toList() }
+ every { mock.add(any()) } answers { operationStoreList.add(firstArg()) }
+ every { mock.remove(any()) } answers {
+ val id = firstArg()
+ operationStoreList.removeIf { it.id == id }
+ }
+ mock
+ }
+
+ val executor = mockk()
+ every { executor.operations } returns listOf("DUMMY_OPERATION")
+ coEvery { executor.execute(any()) } returns ExecutionResponse(ExecutionResult.SUCCESS)
+
+ val operationRepo =
+ spyk(
+ OperationRepo(
+ listOf(executor),
+ operationModelStore,
+ configModelStore,
+ Time(),
+ getNewRecordState(configModelStore),
+ jwtTokenStore,
+ identityModelStore,
+ ),
+ recordPrivateCalls = true,
+ )
+
+ val operation = mockOperation()
+ // externalId starts null — stampExternalId should fill it from the identity model
+
+ // When — enqueue then immediately switch user (simulating LogoutHelper's pattern)
+ operationRepo.enqueue(operation)
+ identityModel.externalId = null
+
+ // Then — the operation should have captured "old-user" before the switch
+ operation.externalId shouldBe "old-user"
+ }
+
+ test("FAIL_UNAUTHORIZED drops operations for anonymous user") {
+ // Given
+ val mocks = Mocks()
+ coEvery { mocks.executor.execute(any()) } returns ExecutionResponse(ExecutionResult.FAIL_UNAUTHORIZED)
+
+ val operation = mockOperation()
+ // externalId defaults to null in mockOperation
+
+ // When
+ mocks.operationRepo.start()
+ val response = mocks.operationRepo.enqueueAndWait(operation)
+
+ // Then
+ response shouldBe false
+ verify { mocks.operationModelStore.remove(operation.id) }
+ }
}) {
companion object {
private fun mockOperation(
@@ -913,6 +1064,9 @@ class OperationRepoTests : FunSpec({
every { operation.modifyComparisonKey } returns modifyComparisonKey
every { operation.translateIds(any()) } just runs
every { operation.applyToRecordId } returns applyToRecordId
+ every { operation.requiresJwt } returns true
+ every { operation.externalId } returns null
+ every { operation.externalId = any() } just runs
return operation
}
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/LoginHelperTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/LoginHelperTests.kt
index a501e73bcf..ec64157170 100644
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/LoginHelperTests.kt
+++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/LoginHelperTests.kt
@@ -6,6 +6,7 @@ import com.onesignal.debug.LogLevel
import com.onesignal.debug.internal.logging.Logging
import com.onesignal.mocks.MockHelper
import com.onesignal.user.internal.identity.IdentityModel
+import com.onesignal.user.internal.identity.JwtTokenStore
import com.onesignal.user.internal.operations.LoginUserOperation
import com.onesignal.user.internal.properties.PropertiesModel
import io.kotest.core.spec.style.FunSpec
@@ -48,6 +49,7 @@ class LoginHelperTests : FunSpec({
val mockOperationRepo = mockk(relaxed = true)
val mockConfigModel = mockk()
every { mockConfigModel.appId } returns appId
+ every { mockConfigModel.useIdentityVerification } returns false
val loginLock = Any()
val loginHelper =
@@ -56,6 +58,7 @@ class LoginHelperTests : FunSpec({
userSwitcher = mockUserSwitcher,
operationRepo = mockOperationRepo,
configModel = mockConfigModel,
+ jwtTokenStore = mockk(relaxed = true),
lock = loginLock,
)
@@ -87,6 +90,7 @@ class LoginHelperTests : FunSpec({
val mockOperationRepo = mockk()
val mockConfigModel = mockk()
every { mockConfigModel.appId } returns appId
+ every { mockConfigModel.useIdentityVerification } returns false
val loginLock = Any()
val userSwitcherSlot = slot<(IdentityModel, PropertiesModel) -> Unit>()
@@ -108,6 +112,7 @@ class LoginHelperTests : FunSpec({
userSwitcher = mockUserSwitcher,
operationRepo = mockOperationRepo,
configModel = mockConfigModel,
+ jwtTokenStore = mockk(relaxed = true),
lock = loginLock,
)
@@ -152,6 +157,7 @@ class LoginHelperTests : FunSpec({
val mockOperationRepo = mockk()
val mockConfigModel = mockk()
every { mockConfigModel.appId } returns appId
+ every { mockConfigModel.useIdentityVerification } returns false
val loginLock = Any()
val userSwitcherSlot = slot<(IdentityModel, PropertiesModel) -> Unit>()
@@ -173,6 +179,7 @@ class LoginHelperTests : FunSpec({
userSwitcher = mockUserSwitcher,
operationRepo = mockOperationRepo,
configModel = mockConfigModel,
+ jwtTokenStore = mockk(relaxed = true),
lock = loginLock,
)
@@ -212,6 +219,7 @@ class LoginHelperTests : FunSpec({
val mockOperationRepo = mockk()
val mockConfigModel = mockk()
every { mockConfigModel.appId } returns appId
+ every { mockConfigModel.useIdentityVerification } returns false
val loginLock = Any()
val userSwitcherSlot = slot<(IdentityModel, PropertiesModel) -> Unit>()
@@ -234,6 +242,7 @@ class LoginHelperTests : FunSpec({
userSwitcher = mockUserSwitcher,
operationRepo = mockOperationRepo,
configModel = mockConfigModel,
+ jwtTokenStore = mockk(relaxed = true),
lock = loginLock,
)
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/LogoutHelperTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/LogoutHelperTests.kt
index 4921ed6bb6..7d4f485952 100644
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/LogoutHelperTests.kt
+++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/LogoutHelperTests.kt
@@ -6,6 +6,7 @@ import com.onesignal.debug.LogLevel
import com.onesignal.debug.internal.logging.Logging
import com.onesignal.mocks.MockHelper
import com.onesignal.user.internal.operations.LoginUserOperation
+import com.onesignal.user.internal.subscriptions.SubscriptionModelStore
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.every
@@ -41,6 +42,7 @@ class LogoutHelperTests : FunSpec({
val mockOperationRepo = mockk(relaxed = true)
val mockConfigModel = mockk()
every { mockConfigModel.appId } returns appId
+ every { mockConfigModel.useIdentityVerification } returns false
val logoutLock = Any()
val logoutHelper =
@@ -49,6 +51,7 @@ class LogoutHelperTests : FunSpec({
userSwitcher = mockUserSwitcher,
operationRepo = mockOperationRepo,
configModel = mockConfigModel,
+ subscriptionModelStore = mockk(relaxed = true),
lock = logoutLock,
)
@@ -71,6 +74,7 @@ class LogoutHelperTests : FunSpec({
val mockOperationRepo = mockk(relaxed = true)
val mockConfigModel = mockk()
every { mockConfigModel.appId } returns appId
+ every { mockConfigModel.useIdentityVerification } returns false
val logoutLock = Any()
val logoutHelper =
@@ -79,6 +83,7 @@ class LogoutHelperTests : FunSpec({
userSwitcher = mockUserSwitcher,
operationRepo = mockOperationRepo,
configModel = mockConfigModel,
+ subscriptionModelStore = mockk(relaxed = true),
lock = logoutLock,
)
@@ -110,6 +115,7 @@ class LogoutHelperTests : FunSpec({
val mockOperationRepo = mockk(relaxed = true)
val mockConfigModel = mockk()
every { mockConfigModel.appId } returns appId
+ every { mockConfigModel.useIdentityVerification } returns false
val logoutLock = Any()
val logoutHelper =
@@ -118,6 +124,7 @@ class LogoutHelperTests : FunSpec({
userSwitcher = mockUserSwitcher,
operationRepo = mockOperationRepo,
configModel = mockConfigModel,
+ subscriptionModelStore = mockk(relaxed = true),
lock = logoutLock,
)
@@ -142,6 +149,7 @@ class LogoutHelperTests : FunSpec({
val mockOperationRepo = mockk(relaxed = true)
val mockConfigModel = mockk()
every { mockConfigModel.appId } returns appId
+ every { mockConfigModel.useIdentityVerification } returns false
val logoutLock = Any()
val logoutHelper =
@@ -150,6 +158,7 @@ class LogoutHelperTests : FunSpec({
userSwitcher = mockUserSwitcher,
operationRepo = mockOperationRepo,
configModel = mockConfigModel,
+ subscriptionModelStore = mockk(relaxed = true),
lock = logoutLock,
)
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/migrations/RecoverFromDroppedLoginBugTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/migrations/RecoverFromDroppedLoginBugTests.kt
index 554c09ac96..0caf8f2f49 100644
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/migrations/RecoverFromDroppedLoginBugTests.kt
+++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/migrations/RecoverFromDroppedLoginBugTests.kt
@@ -6,6 +6,7 @@ import com.onesignal.core.internal.time.impl.Time
import com.onesignal.debug.LogLevel
import com.onesignal.debug.internal.logging.Logging
import com.onesignal.mocks.MockHelper
+import com.onesignal.user.internal.identity.JwtTokenStore
import com.onesignal.user.internal.operations.ExecutorMocks
import com.onesignal.user.internal.operations.LoginUserOperation
import io.kotest.core.spec.style.FunSpec
@@ -38,6 +39,8 @@ private class Mocks {
configModelStore,
Time(),
ExecutorMocks.getNewRecordState(configModelStore),
+ mockk(relaxed = true),
+ MockHelper.identityModelStore(),
),
)
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/CustomEventOperationExecutorTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/CustomEventOperationExecutorTests.kt
index 044d4c3726..5c6d8d6c60 100644
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/CustomEventOperationExecutorTests.kt
+++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/CustomEventOperationExecutorTests.kt
@@ -9,6 +9,7 @@ import com.onesignal.core.internal.operations.ExecutionResult
import com.onesignal.core.internal.operations.Operation
import com.onesignal.mocks.MockHelper
import com.onesignal.user.internal.customEvents.ICustomEventBackendService
+import com.onesignal.user.internal.identity.JwtTokenStore
import com.onesignal.user.internal.operations.impl.executors.CustomEventOperationExecutor
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.equals.shouldBeEqual
@@ -36,7 +37,7 @@ class CustomEventOperationExecutorTests : FunSpec({
val properties = JSONObject().put("key", "value").toString()
val customEventOperationExecutor =
- CustomEventOperationExecutor(mockCustomEventBackendService, mockApplicationService, mockDeviceService)
+ CustomEventOperationExecutor(mockCustomEventBackendService, mockApplicationService, mockDeviceService, mockk(relaxed = true))
val operations = listOf(TrackCustomEventOperation("appId", "onesignalId", null, 1, "event-name", properties))
// When
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/IdentityOperationExecutorTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/IdentityOperationExecutorTests.kt
index 34d0681c48..2ac7861484 100644
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/IdentityOperationExecutorTests.kt
+++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/IdentityOperationExecutorTests.kt
@@ -10,6 +10,7 @@ import com.onesignal.user.internal.backend.IdentityConstants
import com.onesignal.user.internal.builduser.IRebuildUserService
import com.onesignal.user.internal.identity.IdentityModel
import com.onesignal.user.internal.identity.IdentityModelStore
+import com.onesignal.user.internal.identity.JwtTokenStore
import com.onesignal.user.internal.operations.ExecutorMocks.Companion.getNewRecordState
import com.onesignal.user.internal.operations.impl.executors.IdentityOperationExecutor
import io.kotest.core.spec.style.FunSpec
@@ -39,7 +40,7 @@ class IdentityOperationExecutorTests : FunSpec({
val mockBuildUserService = mockk()
val identityOperationExecutor =
- IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState())
+ IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState(), MockHelper.configModelStore(), mockk(relaxed = true))
val operations = listOf(SetAliasOperation("appId", "onesignalId", "aliasKey1", "aliasValue1"))
// When
@@ -69,7 +70,7 @@ class IdentityOperationExecutorTests : FunSpec({
val mockBuildUserService = mockk()
val identityOperationExecutor =
- IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState())
+ IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState(), MockHelper.configModelStore(), mockk(relaxed = true))
val operations = listOf(SetAliasOperation("appId", "onesignalId", "aliasKey1", "aliasValue1"))
// When
@@ -90,7 +91,7 @@ class IdentityOperationExecutorTests : FunSpec({
val mockBuildUserService = mockk()
val identityOperationExecutor =
- IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState())
+ IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState(), MockHelper.configModelStore(), mockk(relaxed = true))
val operations = listOf(SetAliasOperation("appId", "onesignalId", "aliasKey1", "aliasValue1"))
// When
@@ -111,7 +112,7 @@ class IdentityOperationExecutorTests : FunSpec({
every { mockBuildUserService.getRebuildOperationsIfCurrentUser(any(), any()) } returns null
val identityOperationExecutor =
- IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState())
+ IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState(), MockHelper.configModelStore(), mockk(relaxed = true))
val operations = listOf(SetAliasOperation("appId", "onesignalId", "aliasKey1", "aliasValue1"))
// When
@@ -134,7 +135,7 @@ class IdentityOperationExecutorTests : FunSpec({
val mockConfigModelStore = MockHelper.configModelStore().also { it.model.opRepoPostCreateRetryUpTo = 1_000 }
val newRecordState = getNewRecordState(mockConfigModelStore).also { it.add("onesignalId") }
val identityOperationExecutor =
- IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, newRecordState)
+ IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, newRecordState, mockConfigModelStore, mockk(relaxed = true))
val operations = listOf(SetAliasOperation("appId", "onesignalId", "aliasKey1", "aliasValue1"))
// When
@@ -160,7 +161,7 @@ class IdentityOperationExecutorTests : FunSpec({
val mockBuildUserService = mockk()
val identityOperationExecutor =
- IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState())
+ IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState(), MockHelper.configModelStore(), mockk(relaxed = true))
val operations = listOf(DeleteAliasOperation("appId", "onesignalId", "aliasKey1"))
// When
@@ -183,7 +184,7 @@ class IdentityOperationExecutorTests : FunSpec({
val mockBuildUserService = mockk()
val identityOperationExecutor =
- IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState())
+ IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState(), MockHelper.configModelStore(), mockk(relaxed = true))
val operations = listOf(DeleteAliasOperation("appId", "onesignalId", "aliasKey1"))
// When
@@ -203,7 +204,7 @@ class IdentityOperationExecutorTests : FunSpec({
val mockBuildUserService = mockk()
val identityOperationExecutor =
- IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState())
+ IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState(), MockHelper.configModelStore(), mockk(relaxed = true))
val operations = listOf(DeleteAliasOperation("appId", "onesignalId", "aliasKey1"))
// When
@@ -225,7 +226,7 @@ class IdentityOperationExecutorTests : FunSpec({
val mockBuildUserService = mockk()
val identityOperationExecutor =
- IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState())
+ IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState(), MockHelper.configModelStore(), mockk(relaxed = true))
val operations = listOf(DeleteAliasOperation("appId", "onesignalId", "aliasKey1"))
// When
@@ -250,7 +251,7 @@ class IdentityOperationExecutorTests : FunSpec({
val mockConfigModelStore = MockHelper.configModelStore().also { it.model.opRepoPostCreateRetryUpTo = 1_000 }
val newRecordState = getNewRecordState(mockConfigModelStore).also { it.add("onesignalId") }
val identityOperationExecutor =
- IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, newRecordState)
+ IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, newRecordState, mockConfigModelStore, mockk(relaxed = true))
val operations = listOf(DeleteAliasOperation("appId", "onesignalId", "aliasKey1"))
// When
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/LoginUserOperationExecutorTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/LoginUserOperationExecutorTests.kt
index d80dc5531a..001a3d96f4 100644
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/LoginUserOperationExecutorTests.kt
+++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/LoginUserOperationExecutorTests.kt
@@ -14,6 +14,7 @@ import com.onesignal.user.internal.backend.PropertiesObject
import com.onesignal.user.internal.backend.SubscriptionObject
import com.onesignal.user.internal.backend.SubscriptionObjectType
import com.onesignal.user.internal.identity.IdentityModel
+import com.onesignal.user.internal.identity.JwtTokenStore
import com.onesignal.user.internal.operations.impl.executors.IdentityOperationExecutor
import com.onesignal.user.internal.operations.impl.executors.LoginUserOperationExecutor
import com.onesignal.user.internal.properties.PropertiesModel
@@ -53,7 +54,7 @@ class LoginUserOperationExecutorTests : FunSpec({
test("login anonymous user successfully creates user") {
// Given
val mockUserBackendService = mockk()
- coEvery { mockUserBackendService.createUser(any(), any(), any(), any()) } returns
+ coEvery { mockUserBackendService.createUser(any(), any(), any(), any(), any()) } returns
CreateUserResponse(
mapOf(IdentityConstants.ONESIGNAL_ID to remoteOneSignalId),
PropertiesObject(),
@@ -76,6 +77,7 @@ class LoginUserOperationExecutorTests : FunSpec({
mockSubscriptionsModelStore,
MockHelper.configModelStore(),
MockHelper.languageContext(),
+ mockk(relaxed = true),
)
val operations =
listOf(
@@ -94,6 +96,7 @@ class LoginUserOperationExecutorTests : FunSpec({
mapOf(),
any(),
any(),
+ any(),
)
}
}
@@ -101,7 +104,7 @@ class LoginUserOperationExecutorTests : FunSpec({
test("login anonymous user fails with retry when network condition exists") {
// Given
val mockUserBackendService = mockk()
- coEvery { mockUserBackendService.createUser(any(), any(), any(), any()) } throws BackendException(408, "TIMEOUT", retryAfterSeconds = 10)
+ coEvery { mockUserBackendService.createUser(any(), any(), any(), any(), any()) } throws BackendException(408, "TIMEOUT", retryAfterSeconds = 10)
val mockIdentityOperationExecutor = mockk()
@@ -120,6 +123,7 @@ class LoginUserOperationExecutorTests : FunSpec({
mockSubscriptionsModelStore,
MockHelper.configModelStore(),
MockHelper.languageContext(),
+ mockk(relaxed = true),
)
val operations =
listOf(
@@ -133,13 +137,13 @@ class LoginUserOperationExecutorTests : FunSpec({
// Then
response.result shouldBe ExecutionResult.FAIL_RETRY
response.retryAfterSeconds shouldBe 10
- coVerify(exactly = 1) { mockUserBackendService.createUser(appId, mapOf(), any(), any()) }
+ coVerify(exactly = 1) { mockUserBackendService.createUser(appId, mapOf(), any(), any(), any()) }
}
test("login anonymous user fails with no retry when backend error condition exists") {
// Given
val mockUserBackendService = mockk()
- coEvery { mockUserBackendService.createUser(any(), any(), any(), any()) } throws BackendException(404, "NOT FOUND")
+ coEvery { mockUserBackendService.createUser(any(), any(), any(), any(), any()) } throws BackendException(404, "NOT FOUND")
val mockIdentityOperationExecutor = mockk()
@@ -148,7 +152,7 @@ class LoginUserOperationExecutorTests : FunSpec({
val mockSubscriptionsModelStore = mockk()
val loginUserOperationExecutor =
- LoginUserOperationExecutor(mockIdentityOperationExecutor, AndroidMockHelper.applicationService(), MockHelper.deviceService(), mockUserBackendService, mockIdentityModelStore, mockPropertiesModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), MockHelper.languageContext())
+ LoginUserOperationExecutor(mockIdentityOperationExecutor, AndroidMockHelper.applicationService(), MockHelper.deviceService(), mockUserBackendService, mockIdentityModelStore, mockPropertiesModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), MockHelper.languageContext(), mockk(relaxed = true))
val operations =
listOf(
LoginUserOperation(appId, localOneSignalId, null, null),
@@ -160,13 +164,13 @@ class LoginUserOperationExecutorTests : FunSpec({
// Then
response.result shouldBe ExecutionResult.FAIL_PAUSE_OPREPO
- coVerify(exactly = 1) { mockUserBackendService.createUser(appId, mapOf(), any(), any()) }
+ coVerify(exactly = 1) { mockUserBackendService.createUser(appId, mapOf(), any(), any(), any()) }
}
test("login identified user without association successfully creates user") {
// Given
val mockUserBackendService = mockk()
- coEvery { mockUserBackendService.createUser(any(), any(), any(), any()) } returns
+ coEvery { mockUserBackendService.createUser(any(), any(), any(), any(), any()) } returns
CreateUserResponse(mapOf(IdentityConstants.ONESIGNAL_ID to remoteOneSignalId), PropertiesObject(), listOf())
val mockIdentityOperationExecutor = mockk()
@@ -176,7 +180,7 @@ class LoginUserOperationExecutorTests : FunSpec({
val mockSubscriptionsModelStore = mockk()
val loginUserOperationExecutor =
- LoginUserOperationExecutor(mockIdentityOperationExecutor, MockHelper.applicationService(), MockHelper.deviceService(), mockUserBackendService, mockIdentityModelStore, mockPropertiesModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), MockHelper.languageContext())
+ LoginUserOperationExecutor(mockIdentityOperationExecutor, MockHelper.applicationService(), MockHelper.deviceService(), mockUserBackendService, mockIdentityModelStore, mockPropertiesModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), MockHelper.languageContext(), mockk(relaxed = true))
val operations = listOf(LoginUserOperation(appId, localOneSignalId, "externalId", null))
// When
@@ -186,7 +190,7 @@ class LoginUserOperationExecutorTests : FunSpec({
response.result shouldBe ExecutionResult.SUCCESS
coVerify(
exactly = 1,
- ) { mockUserBackendService.createUser(appId, mapOf(IdentityConstants.EXTERNAL_ID to "externalId"), any(), any()) }
+ ) { mockUserBackendService.createUser(appId, mapOf(IdentityConstants.EXTERNAL_ID to "externalId"), any(), any(), any()) }
}
// If the User is identified then the backend may have found an existing User, if so
@@ -194,7 +198,7 @@ class LoginUserOperationExecutorTests : FunSpec({
test("login identified user returns result with RefreshUser") {
// Given
val mockUserBackendService = mockk()
- coEvery { mockUserBackendService.createUser(any(), any(), any(), any()) } returns
+ coEvery { mockUserBackendService.createUser(any(), any(), any(), any(), any()) } returns
CreateUserResponse(mapOf(IdentityConstants.ONESIGNAL_ID to remoteOneSignalId), PropertiesObject(), listOf())
val mockIdentityOperationExecutor = mockk()
@@ -214,6 +218,7 @@ class LoginUserOperationExecutorTests : FunSpec({
mockSubscriptionsModelStore,
MockHelper.configModelStore(),
MockHelper.languageContext(),
+ mockk(relaxed = true),
)
val operations = listOf(LoginUserOperation(appId, localOneSignalId, "externalId", null))
@@ -242,7 +247,7 @@ class LoginUserOperationExecutorTests : FunSpec({
val mockSubscriptionsModelStore = mockk()
val loginUserOperationExecutor =
- LoginUserOperationExecutor(mockIdentityOperationExecutor, MockHelper.applicationService(), MockHelper.deviceService(), mockUserBackendService, mockIdentityModelStore, mockPropertiesModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), MockHelper.languageContext())
+ LoginUserOperationExecutor(mockIdentityOperationExecutor, MockHelper.applicationService(), MockHelper.deviceService(), mockUserBackendService, mockIdentityModelStore, mockPropertiesModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), MockHelper.languageContext(), mockk(relaxed = true))
val operations = listOf(LoginUserOperation(appId, localOneSignalId, "externalId", "existingOneSignalId"))
// When
@@ -267,7 +272,7 @@ class LoginUserOperationExecutorTests : FunSpec({
test("login identified user with association fails with retry when association fails with retry") {
// Given
val mockUserBackendService = mockk()
- coEvery { mockUserBackendService.createUser(any(), any(), any(), any()) } returns
+ coEvery { mockUserBackendService.createUser(any(), any(), any(), any(), any()) } returns
CreateUserResponse(mapOf(IdentityConstants.ONESIGNAL_ID to remoteOneSignalId), PropertiesObject(), listOf())
val mockIdentityOperationExecutor = mockk()
@@ -278,7 +283,7 @@ class LoginUserOperationExecutorTests : FunSpec({
val mockSubscriptionsModelStore = mockk()
val loginUserOperationExecutor =
- LoginUserOperationExecutor(mockIdentityOperationExecutor, MockHelper.applicationService(), MockHelper.deviceService(), mockUserBackendService, mockIdentityModelStore, mockPropertiesModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), MockHelper.languageContext())
+ LoginUserOperationExecutor(mockIdentityOperationExecutor, MockHelper.applicationService(), MockHelper.deviceService(), mockUserBackendService, mockIdentityModelStore, mockPropertiesModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), MockHelper.languageContext(), mockk(relaxed = true))
val operations = listOf(LoginUserOperation(appId, localOneSignalId, "externalId", "existingOneSignalId"))
// When
@@ -303,7 +308,7 @@ class LoginUserOperationExecutorTests : FunSpec({
test("login identified user with association successfully creates user when association fails with no retry") {
// Given
val mockUserBackendService = mockk()
- coEvery { mockUserBackendService.createUser(any(), any(), any(), any()) } returns
+ coEvery { mockUserBackendService.createUser(any(), any(), any(), any(), any()) } returns
CreateUserResponse(mapOf(IdentityConstants.ONESIGNAL_ID to remoteOneSignalId), PropertiesObject(), listOf())
val mockIdentityOperationExecutor = mockk()
@@ -314,7 +319,7 @@ class LoginUserOperationExecutorTests : FunSpec({
val mockSubscriptionsModelStore = mockk()
val loginUserOperationExecutor =
- LoginUserOperationExecutor(mockIdentityOperationExecutor, MockHelper.applicationService(), MockHelper.deviceService(), mockUserBackendService, mockIdentityModelStore, mockPropertiesModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), MockHelper.languageContext())
+ LoginUserOperationExecutor(mockIdentityOperationExecutor, MockHelper.applicationService(), MockHelper.deviceService(), mockUserBackendService, mockIdentityModelStore, mockPropertiesModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), MockHelper.languageContext(), mockk(relaxed = true))
val operations = listOf(LoginUserOperation(appId, localOneSignalId, "externalId", "existingOneSignalId"))
// When
@@ -336,13 +341,13 @@ class LoginUserOperationExecutorTests : FunSpec({
}
coVerify(
exactly = 1,
- ) { mockUserBackendService.createUser(appId, mapOf(IdentityConstants.EXTERNAL_ID to "externalId"), any(), any()) }
+ ) { mockUserBackendService.createUser(appId, mapOf(IdentityConstants.EXTERNAL_ID to "externalId"), any(), any(), any()) }
}
test("login identified user with association fails with retry when association fails with no retry and network condition exists") {
// Given
val mockUserBackendService = mockk()
- coEvery { mockUserBackendService.createUser(any(), any(), any(), any()) } throws BackendException(408, "TIMEOUT")
+ coEvery { mockUserBackendService.createUser(any(), any(), any(), any(), any()) } throws BackendException(408, "TIMEOUT")
val mockIdentityOperationExecutor = mockk()
coEvery { mockIdentityOperationExecutor.execute(any()) } returns ExecutionResponse(ExecutionResult.FAIL_NORETRY)
@@ -352,7 +357,7 @@ class LoginUserOperationExecutorTests : FunSpec({
val mockSubscriptionsModelStore = mockk()
val loginUserOperationExecutor =
- LoginUserOperationExecutor(mockIdentityOperationExecutor, MockHelper.applicationService(), MockHelper.deviceService(), mockUserBackendService, mockIdentityModelStore, mockPropertiesModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), MockHelper.languageContext())
+ LoginUserOperationExecutor(mockIdentityOperationExecutor, MockHelper.applicationService(), MockHelper.deviceService(), mockUserBackendService, mockIdentityModelStore, mockPropertiesModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), MockHelper.languageContext(), mockk(relaxed = true))
val operations = listOf(LoginUserOperation(appId, localOneSignalId, "externalId", "existingOneSignalId"))
// When
@@ -374,13 +379,13 @@ class LoginUserOperationExecutorTests : FunSpec({
}
coVerify(
exactly = 1,
- ) { mockUserBackendService.createUser(appId, mapOf(IdentityConstants.EXTERNAL_ID to "externalId"), any(), any()) }
+ ) { mockUserBackendService.createUser(appId, mapOf(IdentityConstants.EXTERNAL_ID to "externalId"), any(), any(), any()) }
}
test("creating user will merge operations into one backend call") {
// Given
val mockUserBackendService = mockk()
- coEvery { mockUserBackendService.createUser(any(), any(), any(), any()) } returns
+ coEvery { mockUserBackendService.createUser(any(), any(), any(), any(), any()) } returns
CreateUserResponse(
mapOf(IdentityConstants.ONESIGNAL_ID to remoteOneSignalId),
PropertiesObject(),
@@ -403,6 +408,7 @@ class LoginUserOperationExecutorTests : FunSpec({
mockSubscriptionsModelStore,
MockHelper.configModelStore(),
MockHelper.languageContext(),
+ mockk(relaxed = true),
)
val operations =
listOf(
@@ -459,6 +465,7 @@ class LoginUserOperationExecutorTests : FunSpec({
SubscriptionStatus.fromInt(subscription.notificationTypes!!) shouldBe SubscriptionStatus.SUBSCRIBED
},
any(),
+ any(),
)
}
}
@@ -466,7 +473,7 @@ class LoginUserOperationExecutorTests : FunSpec({
test("creating user will hydrate when the user hasn't changed") {
// Given
val mockUserBackendService = mockk()
- coEvery { mockUserBackendService.createUser(any(), any(), any(), any()) } returns
+ coEvery { mockUserBackendService.createUser(any(), any(), any(), any(), any()) } returns
CreateUserResponse(
mapOf(IdentityConstants.ONESIGNAL_ID to remoteOneSignalId),
PropertiesObject(),
@@ -504,6 +511,7 @@ class LoginUserOperationExecutorTests : FunSpec({
mockSubscriptionsModelStore,
MockHelper.configModelStore(),
MockHelper.languageContext(),
+ mockk(relaxed = true),
)
val operations =
listOf(
@@ -545,6 +553,7 @@ class LoginUserOperationExecutorTests : FunSpec({
mapOf(),
any(),
any(),
+ any(),
)
}
}
@@ -552,7 +561,7 @@ class LoginUserOperationExecutorTests : FunSpec({
test("creating user will not hydrate when the user has changed") {
// Given
val mockUserBackendService = mockk()
- coEvery { mockUserBackendService.createUser(any(), any(), any(), any()) } returns
+ coEvery { mockUserBackendService.createUser(any(), any(), any(), any(), any()) } returns
CreateUserResponse(
mapOf(IdentityConstants.ONESIGNAL_ID to remoteOneSignalId),
PropertiesObject(),
@@ -590,6 +599,7 @@ class LoginUserOperationExecutorTests : FunSpec({
mockSubscriptionsModelStore,
MockHelper.configModelStore(),
MockHelper.languageContext(),
+ mockk(relaxed = true),
)
val operations =
listOf(
@@ -631,6 +641,7 @@ class LoginUserOperationExecutorTests : FunSpec({
mapOf(),
any(),
any(),
+ any(),
)
}
}
@@ -638,7 +649,7 @@ class LoginUserOperationExecutorTests : FunSpec({
test("creating user will provide local to remote translations") {
// Given
val mockUserBackendService = mockk()
- coEvery { mockUserBackendService.createUser(any(), any(), any(), any()) } returns
+ coEvery { mockUserBackendService.createUser(any(), any(), any(), any(), any()) } returns
CreateUserResponse(
mapOf(IdentityConstants.ONESIGNAL_ID to remoteOneSignalId),
PropertiesObject(),
@@ -662,6 +673,7 @@ class LoginUserOperationExecutorTests : FunSpec({
mockSubscriptionsModelStore,
MockHelper.configModelStore(),
MockHelper.languageContext(),
+ mockk(relaxed = true),
)
val operations =
listOf(
@@ -698,6 +710,7 @@ class LoginUserOperationExecutorTests : FunSpec({
mapOf(),
any(),
any(),
+ any(),
)
}
}
@@ -705,7 +718,7 @@ class LoginUserOperationExecutorTests : FunSpec({
test("ensure anonymous login with no other operations will fail with FAIL_NORETRY") {
// Given
val mockUserBackendService = mockk()
- coEvery { mockUserBackendService.createUser(any(), any(), any(), any()) } returns
+ coEvery { mockUserBackendService.createUser(any(), any(), any(), any(), any()) } returns
CreateUserResponse(mapOf(IdentityConstants.ONESIGNAL_ID to remoteOneSignalId), PropertiesObject(), listOf())
val mockIdentityOperationExecutor = mockk()
@@ -725,6 +738,7 @@ class LoginUserOperationExecutorTests : FunSpec({
mockSubscriptionsModelStore,
MockHelper.configModelStore(),
MockHelper.languageContext(),
+ mockk(relaxed = true),
)
// anonymous Login request
val operations = listOf(LoginUserOperation(appId, localOneSignalId, null, null))
@@ -737,14 +751,14 @@ class LoginUserOperationExecutorTests : FunSpec({
// ensure user is not created by the bad request
coVerify(
exactly = 0,
- ) { mockUserBackendService.createUser(appId, any(), any(), any()) }
+ ) { mockUserBackendService.createUser(appId, any(), any(), any(), any()) }
}
test("create user maps subscriptions when backend order is different (match by id/token)") {
// Given
val mockUserBackendService = mockk()
// backend returns EMAIL first (with token), then PUSH — out of order
- coEvery { mockUserBackendService.createUser(any(), any(), any(), any()) } returns
+ coEvery { mockUserBackendService.createUser(any(), any(), any(), any(), any()) } returns
CreateUserResponse(
mapOf(IdentityConstants.ONESIGNAL_ID to remoteOneSignalId),
PropertiesObject(),
@@ -771,6 +785,7 @@ class LoginUserOperationExecutorTests : FunSpec({
mockSubscriptionsModelStore,
MockHelper.configModelStore(),
MockHelper.languageContext(),
+ mockk(relaxed = true),
)
// send PUSH then EMAIL (local IDs 1,2) — order differs from backend response
@@ -795,14 +810,14 @@ class LoginUserOperationExecutorTests : FunSpec({
// email
localSubscriptionId2 to remoteSubscriptionId2,
)
- coVerify(exactly = 1) { mockUserBackendService.createUser(appId, mapOf(), any(), any()) }
+ coVerify(exactly = 1) { mockUserBackendService.createUser(appId, mapOf(), any(), any(), any()) }
}
test("create user maps push subscription by type when id and token don't match (case for deleted push sub)") {
// Given
val mockUserBackendService = mockk()
// simulate server-side push sub recreated with new ID and no token; must match by type
- coEvery { mockUserBackendService.createUser(any(), any(), any(), any()) } returns
+ coEvery { mockUserBackendService.createUser(any(), any(), any(), any(), any()) } returns
CreateUserResponse(
mapOf(IdentityConstants.ONESIGNAL_ID to remoteOneSignalId),
PropertiesObject(),
@@ -835,6 +850,7 @@ class LoginUserOperationExecutorTests : FunSpec({
mockSubscriptionsModelStore,
configModelStore,
MockHelper.languageContext(),
+ mockk(relaxed = true),
)
val ops =
@@ -857,6 +873,6 @@ class LoginUserOperationExecutorTests : FunSpec({
localPushModel.id shouldBe remoteSubscriptionId1
// pushSubscriptionId should be updated from local to remote id
configModelStore.model.pushSubscriptionId shouldBe remoteSubscriptionId1
- coVerify(exactly = 1) { mockUserBackendService.createUser(appId, mapOf(), any(), any()) }
+ coVerify(exactly = 1) { mockUserBackendService.createUser(appId, mapOf(), any(), any(), any()) }
}
})
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/RefreshUserOperationExecutorTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/RefreshUserOperationExecutorTests.kt
index 2689d761af..a32a004c73 100644
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/RefreshUserOperationExecutorTests.kt
+++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/RefreshUserOperationExecutorTests.kt
@@ -14,6 +14,7 @@ import com.onesignal.user.internal.backend.SubscriptionObject
import com.onesignal.user.internal.backend.SubscriptionObjectType
import com.onesignal.user.internal.builduser.IRebuildUserService
import com.onesignal.user.internal.identity.IdentityModel
+import com.onesignal.user.internal.identity.JwtTokenStore
import com.onesignal.user.internal.operations.ExecutorMocks.Companion.getNewRecordState
import com.onesignal.user.internal.operations.impl.executors.RefreshUserOperationExecutor
import com.onesignal.user.internal.properties.PropertiesModel
@@ -107,6 +108,7 @@ class RefreshUserOperationExecutorTests : FunSpec({
mockConfigModelStore,
mockBuildUserService,
getNewRecordState(),
+ mockk(relaxed = true),
)
val operations = listOf(RefreshUserOperation(appId, remoteOneSignalId))
@@ -191,6 +193,7 @@ class RefreshUserOperationExecutorTests : FunSpec({
MockHelper.configModelStore(),
mockBuildUserService,
getNewRecordState(),
+ mockk(relaxed = true),
)
val operations = listOf(RefreshUserOperation(appId, remoteOneSignalId))
@@ -230,6 +233,7 @@ class RefreshUserOperationExecutorTests : FunSpec({
MockHelper.configModelStore(),
mockBuildUserService,
getNewRecordState(),
+ mockk(relaxed = true),
)
val operations = listOf(RefreshUserOperation(appId, remoteOneSignalId))
@@ -265,6 +269,7 @@ class RefreshUserOperationExecutorTests : FunSpec({
MockHelper.configModelStore(),
mockBuildUserService,
getNewRecordState(),
+ mockk(relaxed = true),
)
val operations = listOf(RefreshUserOperation(appId, remoteOneSignalId))
@@ -300,6 +305,7 @@ class RefreshUserOperationExecutorTests : FunSpec({
MockHelper.configModelStore(),
mockBuildUserService,
getNewRecordState(),
+ mockk(relaxed = true),
)
val operations = listOf(RefreshUserOperation(appId, remoteOneSignalId))
@@ -337,6 +343,7 @@ class RefreshUserOperationExecutorTests : FunSpec({
MockHelper.configModelStore(),
mockBuildUserService,
newRecordState,
+ mockk(relaxed = true),
)
val operations = listOf(RefreshUserOperation(appId, remoteOneSignalId))
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/SubscriptionOperationExecutorTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/SubscriptionOperationExecutorTests.kt
index 4ae3053247..1658207431 100644
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/SubscriptionOperationExecutorTests.kt
+++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/SubscriptionOperationExecutorTests.kt
@@ -13,6 +13,7 @@ import com.onesignal.user.internal.backend.ISubscriptionBackendService
import com.onesignal.user.internal.backend.IdentityConstants
import com.onesignal.user.internal.backend.SubscriptionObjectType
import com.onesignal.user.internal.builduser.IRebuildUserService
+import com.onesignal.user.internal.identity.JwtTokenStore
import com.onesignal.user.internal.operations.ExecutorMocks.Companion.getNewRecordState
import com.onesignal.user.internal.operations.impl.executors.SubscriptionOperationExecutor
import com.onesignal.user.internal.subscriptions.SubscriptionModel
@@ -68,6 +69,7 @@ class SubscriptionOperationExecutorTests :
mockBuildUserService,
getNewRecordState(),
mockConsistencyManager,
+ mockk(relaxed = true),
)
val operations =
@@ -128,6 +130,7 @@ class SubscriptionOperationExecutorTests :
mockBuildUserService,
getNewRecordState(),
mockConsistencyManager,
+ mockk(relaxed = true),
)
val operations =
@@ -178,6 +181,7 @@ class SubscriptionOperationExecutorTests :
mockBuildUserService,
getNewRecordState(),
mockConsistencyManager,
+ mockk(relaxed = true),
)
val operations =
@@ -233,6 +237,7 @@ class SubscriptionOperationExecutorTests :
mockBuildUserService,
getNewRecordState(),
mockConsistencyManager,
+ mockk(relaxed = true),
)
val operations =
@@ -288,6 +293,7 @@ class SubscriptionOperationExecutorTests :
mockBuildUserService,
newRecordState,
mockConsistencyManager,
+ mockk(relaxed = true),
)
val operations =
@@ -331,6 +337,7 @@ class SubscriptionOperationExecutorTests :
mockBuildUserService,
getNewRecordState(),
mockConsistencyManager,
+ mockk(relaxed = true),
)
val operations =
@@ -377,6 +384,7 @@ class SubscriptionOperationExecutorTests :
mockBuildUserService,
getNewRecordState(),
mockConsistencyManager,
+ mockk(relaxed = true),
)
val operations =
@@ -447,6 +455,7 @@ class SubscriptionOperationExecutorTests :
mockBuildUserService,
getNewRecordState(),
mockConsistencyManager,
+ mockk(relaxed = true),
)
val operations =
@@ -508,6 +517,7 @@ class SubscriptionOperationExecutorTests :
mockBuildUserService,
getNewRecordState(),
mockConsistencyManager,
+ mockk(relaxed = true),
)
val operations =
@@ -560,6 +570,7 @@ class SubscriptionOperationExecutorTests :
mockBuildUserService,
getNewRecordState(),
mockConsistencyManager,
+ mockk(relaxed = true),
)
val operations =
@@ -614,6 +625,7 @@ class SubscriptionOperationExecutorTests :
mockBuildUserService,
newRecordState,
mockConsistencyManager,
+ mockk(relaxed = true),
)
val operations =
@@ -656,6 +668,7 @@ class SubscriptionOperationExecutorTests :
mockBuildUserService,
getNewRecordState(),
mockConsistencyManager,
+ mockk(relaxed = true),
)
val operations =
@@ -690,6 +703,7 @@ class SubscriptionOperationExecutorTests :
mockBuildUserService,
getNewRecordState(),
mockConsistencyManager,
+ mockk(relaxed = true),
)
val operations =
@@ -725,6 +739,7 @@ class SubscriptionOperationExecutorTests :
mockBuildUserService,
getNewRecordState(),
mockConsistencyManager,
+ mockk(relaxed = true),
)
val operations =
@@ -760,6 +775,7 @@ class SubscriptionOperationExecutorTests :
mockBuildUserService,
newRecordState,
mockConsistencyManager,
+ mockk(relaxed = true),
)
val operations =
@@ -801,6 +817,7 @@ class SubscriptionOperationExecutorTests :
mockBuildUserService,
getNewRecordState(),
mockConsistencyManager,
+ mockk(relaxed = true),
)
val operations =
diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/UpdateUserOperationExecutorTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/UpdateUserOperationExecutorTests.kt
index de2e148ff8..de3ff7b89b 100644
--- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/UpdateUserOperationExecutorTests.kt
+++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/UpdateUserOperationExecutorTests.kt
@@ -10,6 +10,7 @@ import com.onesignal.mocks.MockHelper
import com.onesignal.user.internal.backend.IUserBackendService
import com.onesignal.user.internal.backend.IdentityConstants
import com.onesignal.user.internal.builduser.IRebuildUserService
+import com.onesignal.user.internal.identity.JwtTokenStore
import com.onesignal.user.internal.operations.ExecutorMocks.Companion.getNewRecordState
import com.onesignal.user.internal.operations.impl.executors.UpdateUserOperationExecutor
import com.onesignal.user.internal.properties.PropertiesModel
@@ -56,6 +57,8 @@ class UpdateUserOperationExecutorTests :
mockBuildUserService,
getNewRecordState(),
mockConsistencyManager,
+ MockHelper.configModelStore(),
+ mockk(relaxed = true),
)
val operations = listOf(SetTagOperation(appId, remoteOneSignalId, "tagKey1", "tagValue1"))
@@ -96,6 +99,8 @@ class UpdateUserOperationExecutorTests :
mockBuildUserService,
getNewRecordState(),
mockConsistencyManager,
+ MockHelper.configModelStore(),
+ mockk(relaxed = true),
)
val operations =
listOf(
@@ -158,6 +163,8 @@ class UpdateUserOperationExecutorTests :
mockBuildUserService,
getNewRecordState(),
mockConsistencyManager,
+ MockHelper.configModelStore(),
+ mockk(relaxed = true),
)
val operations =
listOf(
@@ -203,6 +210,8 @@ class UpdateUserOperationExecutorTests :
mockBuildUserService,
getNewRecordState(),
mockConsistencyManager,
+ MockHelper.configModelStore(),
+ mockk(relaxed = true),
)
val operations =
listOf(
@@ -268,6 +277,8 @@ class UpdateUserOperationExecutorTests :
mockBuildUserService,
getNewRecordState(),
mockConsistencyManager,
+ MockHelper.configModelStore(),
+ mockk(relaxed = true),
)
val operations =
listOf(
@@ -316,6 +327,8 @@ class UpdateUserOperationExecutorTests :
mockBuildUserService,
getNewRecordState(),
mockConsistencyManager,
+ MockHelper.configModelStore(),
+ mockk(relaxed = true),
)
val operations = listOf(SetTagOperation(appId, remoteOneSignalId, "tagKey1", "tagValue1"))
@@ -349,6 +362,8 @@ class UpdateUserOperationExecutorTests :
mockBuildUserService,
newRecordState,
mockConsistencyManager,
+ MockHelper.configModelStore(),
+ mockk(relaxed = true),
)
val operations = listOf(SetTagOperation(appId, remoteOneSignalId, "tagKey1", "tagValue1"))
@@ -379,6 +394,8 @@ class UpdateUserOperationExecutorTests :
mockBuildUserService,
getNewRecordState(),
mockConsistencyManager,
+ MockHelper.configModelStore(),
+ mockk(relaxed = true),
)
val operations =
diff --git a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/InAppMessagesManager.kt b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/InAppMessagesManager.kt
index b4994a0aeb..80e4ba4f93 100644
--- a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/InAppMessagesManager.kt
+++ b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/InAppMessagesManager.kt
@@ -48,6 +48,7 @@ import com.onesignal.user.IUserManager
import com.onesignal.user.internal.backend.IdentityConstants
import com.onesignal.user.internal.identity.IdentityModel
import com.onesignal.user.internal.identity.IdentityModelStore
+import com.onesignal.user.internal.identity.JwtTokenStore
import com.onesignal.user.internal.subscriptions.ISubscriptionChangedHandler
import com.onesignal.user.internal.subscriptions.ISubscriptionManager
import com.onesignal.user.internal.subscriptions.SubscriptionModel
@@ -76,6 +77,7 @@ internal class InAppMessagesManager(
private val _languageContext: ILanguageContext,
private val _time: ITime,
private val _consistencyManager: IConsistencyManager,
+ private val _jwtTokenStore: JwtTokenStore,
) : IInAppMessagesManager,
IStartableService,
ISubscriptionChangedHandler,
@@ -299,6 +301,12 @@ internal class InAppMessagesManager(
return
}
+ val externalId = _identityModelStore.model.externalId
+ if (_configModelStore.model.useIdentityVerification == true && externalId == null) {
+ Logging.debug("InAppMessagesManager.fetchMessages: Skipping IAM fetch for anonymous user while identity verification is enabled.")
+ return
+ }
+
fetchIAMMutex.withLock {
val now = _time.currentTimeMillis
if (lastTimeFetchedIAMs != null && (now - lastTimeFetchedIAMs!!) < _configModelStore.model.fetchIAMMinInterval) {
@@ -308,9 +316,17 @@ internal class InAppMessagesManager(
lastTimeFetchedIAMs = now
}
+ val (aliasLabel, aliasValue) =
+ IdentityConstants.resolveAlias(
+ _configModelStore.model.useIdentityVerification,
+ externalId,
+ _identityModelStore.model.onesignalId,
+ )
+ val jwt = externalId?.let { _jwtTokenStore.getJwt(it) }
+
// lambda so that it is updated on each potential retry
val sessionDurationProvider = { _time.currentTimeMillis - _sessionService.startTime }
- val newMessages = _backend.listInAppMessages(appId, subscriptionId, rywData, sessionDurationProvider)
+ val newMessages = _backend.listInAppMessages(appId, aliasLabel, aliasValue, subscriptionId, rywData, sessionDurationProvider, jwt)
if (newMessages != null) {
this.messages = newMessages as MutableList
diff --git a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/backend/IInAppBackendService.kt b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/backend/IInAppBackendService.kt
index 6755b6eb5a..7044d6db3b 100644
--- a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/backend/IInAppBackendService.kt
+++ b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/backend/IInAppBackendService.kt
@@ -21,9 +21,12 @@ internal interface IInAppBackendService {
*/
suspend fun listInAppMessages(
appId: String,
+ aliasLabel: String,
+ aliasValue: String,
subscriptionId: String,
rywData: RywData,
sessionDurationProvider: () -> Long,
+ jwt: String? = null,
): List?
/**
diff --git a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/backend/impl/InAppBackendService.kt b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/backend/impl/InAppBackendService.kt
index 9bbd738d55..77a77b5f5f 100644
--- a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/backend/impl/InAppBackendService.kt
+++ b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/backend/impl/InAppBackendService.kt
@@ -26,15 +26,18 @@ internal class InAppBackendService(
override suspend fun listInAppMessages(
appId: String,
+ aliasLabel: String,
+ aliasValue: String,
subscriptionId: String,
rywData: RywData,
sessionDurationProvider: () -> Long,
+ jwt: String?,
): List? {
val rywDelay = rywData.rywDelay ?: DEFAULT_RYW_DELAY_MS
delay(rywDelay) // Delay by the specified amount
- val baseUrl = "apps/$appId/subscriptions/$subscriptionId/iams"
- return attemptFetchWithRetries(baseUrl, rywData, sessionDurationProvider)
+ val baseUrl = "apps/$appId/users/by/$aliasLabel/$aliasValue/subscriptions/$subscriptionId/iams"
+ return attemptFetchWithRetries(baseUrl, rywData, sessionDurationProvider, jwt)
}
override suspend fun getIAMData(
@@ -209,6 +212,7 @@ internal class InAppBackendService(
baseUrl: String,
rywData: RywData,
sessionDurationProvider: () -> Long,
+ jwt: String? = null,
): List? {
var attempts = 0
var retryLimit: Int = 0 // retry limit is remote defined & set dynamically below
@@ -220,6 +224,7 @@ internal class InAppBackendService(
rywToken = rywData.rywToken,
sessionDuration = sessionDurationProvider(),
retryCount = retryCount,
+ jwt = jwt,
)
val response = _httpClient.get(baseUrl, values)
@@ -244,18 +249,20 @@ internal class InAppBackendService(
} while (attempts <= retryLimit)
// Final attempt without the RYW token if retries fail
- return fetchInAppMessagesWithoutRywToken(baseUrl, sessionDurationProvider)
+ return fetchInAppMessagesWithoutRywToken(baseUrl, sessionDurationProvider, jwt)
}
private suspend fun fetchInAppMessagesWithoutRywToken(
url: String,
sessionDurationProvider: () -> Long,
+ jwt: String? = null,
): List? {
val response =
_httpClient.get(
url,
OptionalHeaders(
sessionDuration = sessionDurationProvider(),
+ jwt = jwt,
),
)
diff --git a/OneSignalSDK/onesignal/in-app-messages/src/test/java/com/onesignal/inAppMessages/internal/InAppMessagesManagerTests.kt b/OneSignalSDK/onesignal/in-app-messages/src/test/java/com/onesignal/inAppMessages/internal/InAppMessagesManagerTests.kt
index 418cce53cc..60a75847bd 100644
--- a/OneSignalSDK/onesignal/in-app-messages/src/test/java/com/onesignal/inAppMessages/internal/InAppMessagesManagerTests.kt
+++ b/OneSignalSDK/onesignal/in-app-messages/src/test/java/com/onesignal/inAppMessages/internal/InAppMessagesManagerTests.kt
@@ -31,6 +31,7 @@ import com.onesignal.session.internal.influence.IInfluenceManager
import com.onesignal.session.internal.outcomes.IOutcomeEventsController
import com.onesignal.session.internal.session.ISessionService
import com.onesignal.user.IUserManager
+import com.onesignal.user.internal.identity.JwtTokenStore
import com.onesignal.user.internal.subscriptions.ISubscriptionManager
import com.onesignal.user.internal.subscriptions.SubscriptionModel
import com.onesignal.user.subscriptions.IPushSubscription
@@ -77,7 +78,7 @@ private class Mocks {
val inAppLifecycleService = mockk(relaxed = true)
val languageContext = MockHelper.languageContext()
val time = MockHelper.time(1000)
- val inAppMessageLifecycleListener = spyk()
+ val inAppMessageLifecycleListener = mockk