diff --git a/OneSignalSDK/detekt/detekt-baseline-core.xml b/OneSignalSDK/detekt/detekt-baseline-core.xml index 3f07ef1a13..88b28052a4 100644 --- a/OneSignalSDK/detekt/detekt-baseline-core.xml +++ b/OneSignalSDK/detekt/detekt-baseline-core.xml @@ -42,6 +42,7 @@ 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 _identityModelStore: IdentityModelStore ConstructorParameterNaming:LoginUserFromSubscriptionOperationExecutor.kt$LoginUserFromSubscriptionOperationExecutor$private val _propertiesModelStore: PropertiesModelStore 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 9998fd213e..908f55a0d4 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 @@ -45,6 +45,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.jwt.JwtTokenStore internal class CoreModule : IModule { override fun register(builder: ServiceBuilder) { @@ -68,6 +69,8 @@ internal class CoreModule : IModule { builder.register().provides() 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/config/ConfigModel.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt index 616fa1916b..acc89a26ca 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 @@ -2,6 +2,7 @@ package com.onesignal.core.internal.config import com.onesignal.common.modeling.Model import com.onesignal.core.internal.http.OneSignalService.ONESIGNAL_API_BASE_URL +import com.onesignal.user.internal.jwt.JwtRequirement import org.json.JSONArray import org.json.JSONObject @@ -236,13 +237,18 @@ class ConfigModel : Model() { setBooleanProperty(::enterprise.name, value) } - /** - * Whether SMS auth hash should be used. - */ - var useIdentityVerification: Boolean - get() = getBooleanProperty(::useIdentityVerification.name) { false } + /** Mirrors backend `jwt_required`. Pre-HYDRATE callers see [JwtRequirement.UNKNOWN]. */ + internal var useIdentityVerification: JwtRequirement + get() = JwtRequirement.fromBoolean(getOptBooleanProperty(::useIdentityVerification.name)) set(value) { - setBooleanProperty(::useIdentityVerification.name, value) + setOptBooleanProperty( + ::useIdentityVerification.name, + when (value) { + JwtRequirement.UNKNOWN -> null + JwtRequirement.NOT_REQUIRED -> false + JwtRequirement.REQUIRED -> true + }, + ) } /** diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/impl/ConfigModelStoreListener.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/impl/ConfigModelStoreListener.kt index e15b6faa07..26d4e1b86e 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/impl/ConfigModelStoreListener.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/impl/ConfigModelStoreListener.kt @@ -10,6 +10,7 @@ import com.onesignal.core.internal.config.ConfigModel import com.onesignal.core.internal.config.ConfigModelStore import com.onesignal.core.internal.startup.IStartableService import com.onesignal.debug.internal.logging.Logging +import com.onesignal.user.internal.jwt.JwtRequirement import com.onesignal.user.internal.subscriptions.ISubscriptionManager import kotlinx.coroutines.delay import java.net.HttpURLConnection @@ -85,7 +86,9 @@ internal class ConfigModelStoreListener( // these are only copied from the backend params when the backend has set them. params.enterprise?.let { config.enterprise = it } - params.useIdentityVerification?.let { config.useIdentityVerification = it } + params.useIdentityVerification?.let { + config.useIdentityVerification = JwtRequirement.fromBoolean(it) + } params.firebaseAnalytics?.let { config.firebaseAnalytics = it } params.restoreTTLFilter?.let { config.restoreTTLFilter = it } params.clearGroupOnSummaryClick?.let { config.clearGroupOnSummaryClick = it } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/features/FeatureFlag.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/features/FeatureFlag.kt index 324421a71a..a2a5b33155 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/features/FeatureFlag.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/features/FeatureFlag.kt @@ -25,13 +25,17 @@ internal enum class FeatureFlag( val activationMode: FeatureActivationMode ) { // Threading mode is selected once per app startup to avoid mixed-mode behavior mid-session. - // // Remote key (lowercase) must match backend / Turbine flag id. - // SDK_BACKGROUND_THREADING( "sdk_background_threading", FeatureActivationMode.APP_STARTUP ), + + /** JWT signing of SDK requests. IMMEDIATE so a kill-switch doesn't need a cold start. */ + SDK_IDENTITY_VERIFICATION( + "sdk_identity_verification", + FeatureActivationMode.IMMEDIATE + ), ; fun isEnabledIn(enabledKeys: Set): Boolean { diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/features/FeatureManager.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/features/FeatureManager.kt index e9b25c002c..04918d90f7 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/features/FeatureManager.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/features/FeatureManager.kt @@ -8,6 +8,7 @@ import com.onesignal.core.internal.backend.impl.FeatureFlagsJsonParser import com.onesignal.core.internal.config.ConfigModel import com.onesignal.core.internal.config.ConfigModelStore import com.onesignal.debug.internal.logging.Logging +import com.onesignal.user.internal.jwt.IdentityVerificationGates import kotlinx.serialization.json.JsonObject internal interface IFeatureManager { @@ -163,6 +164,13 @@ internal class FeatureManager( enabled = enabled, source = "FeatureManager:${feature.activationMode}" ) + + FeatureFlag.SDK_IDENTITY_VERIFICATION -> + IdentityVerificationGates.update( + featureFlagOn = enabled, + jwtRequirement = configModelStore.model.useIdentityVerification, + source = "FeatureManager:${feature.activationMode}" + ) } } 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..708c715d6f 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,23 @@ abstract class Operation(name: String) : Model() { setStringProperty(::name.name, value) } + /** + * externalId of the user this operation was enqueued for. Captured at construction + * time so the op stays bound to its original user even if the current user changes + * before the op executes. Null for anonymous users. + */ + var externalId: String? + get() = getOptStringProperty(::externalId.name) + internal set(value) { // `internal` so subclass constructors can assign at construction time + setOptStringProperty(::externalId.name, value) + } + + /** + * Whether this operation requires a valid JWT when Identity Verification is active. + * Subclasses may override to `false` for endpoints that don't require auth. + */ + open val requiresJwt: Boolean get() = true + init { this.name = name } 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..84fab69449 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 @@ -204,6 +204,9 @@ object PreferenceOneSignalKeys { */ const val PREFS_OS_LOCATION_SHARED = "OS_LOCATION_SHARED" + /** (String) JSON object mapping externalId -> JWT token. */ + const val PREFS_OS_JWT_TOKENS = "PREFS_OS_JWT_TOKENS" + // Permissions /** diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/purchases/impl/TrackGooglePurchase.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/purchases/impl/TrackGooglePurchase.kt index 35f95fac5f..8b2694bcb7 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/purchases/impl/TrackGooglePurchase.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/purchases/impl/TrackGooglePurchase.kt @@ -240,6 +240,7 @@ internal class TrackGooglePurchase( TrackPurchaseOperation( _configModelStore.model.appId, _identityModelStore.model.onesignalId, + _identityModelStore.model.externalId, newAsExisting, BigDecimal(0), purchasesToReport, diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/session/impl/SessionListener.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/session/impl/SessionListener.kt index 1fcdcd8641..344f7702ed 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/session/impl/SessionListener.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/session/internal/session/impl/SessionListener.kt @@ -44,7 +44,14 @@ internal class SessionListener( override fun onSessionStarted() { _propertiesModelStore.model.timezone = TimeUtils.getTimeZoneId() - _operationRepo.enqueue(TrackSessionStartOperation(_configModelStore.model.appId, _identityModelStore.model.onesignalId), true) + _operationRepo.enqueue( + TrackSessionStartOperation( + _configModelStore.model.appId, + _identityModelStore.model.onesignalId, + _identityModelStore.model.externalId, + ), + true, + ) } override fun onSessionActive() { @@ -60,7 +67,12 @@ internal class SessionListener( } _operationRepo.enqueue( - TrackSessionEndOperation(_configModelStore.model.appId, _identityModelStore.model.onesignalId, durationInSeconds), + TrackSessionEndOperation( + _configModelStore.model.appId, + _identityModelStore.model.onesignalId, + _identityModelStore.model.externalId, + durationInSeconds, + ), ) suspendifyOnIO { diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/UserSwitcher.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/UserSwitcher.kt index 5fba367b1a..f1401e031a 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/UserSwitcher.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/UserSwitcher.kt @@ -173,6 +173,7 @@ class UserSwitcher( LoginUserFromSubscriptionOperation( configModel.appId, identityModelStore.model.onesignalId, + identityModelStore.model.externalId, legacyPlayerId, ), ) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/builduser/impl/RebuildUserService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/builduser/impl/RebuildUserService.kt index a9f42bcfe1..22442072e5 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/builduser/impl/RebuildUserService.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/builduser/impl/RebuildUserService.kt @@ -52,6 +52,7 @@ class RebuildUserService( CreateSubscriptionOperation( appId, onesignalId, + identityModel.externalId, pushSubscription.id, pushSubscription.type, pushSubscription.optedIn, @@ -60,7 +61,7 @@ class RebuildUserService( ), ) } - operations.add(RefreshUserOperation(appId, onesignalId)) + operations.add(RefreshUserOperation(appId, onesignalId, identityModel.externalId)) return operations } } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/jwt/IJwtUpdateListener.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/jwt/IJwtUpdateListener.kt new file mode 100644 index 0000000000..63b6a4075d --- /dev/null +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/jwt/IJwtUpdateListener.kt @@ -0,0 +1,10 @@ +package com.onesignal.user.internal.jwt + +/** + * Wake-up notification from [JwtTokenStore] when the JWT for [externalId] changes. + * Listeners must call [JwtTokenStore.getJwt] for the current value — event delivery + * order is not guaranteed to match mutation order across concurrent writers. + */ +internal interface IJwtUpdateListener { + fun onJwtUpdated(externalId: String) +} diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/jwt/IdentityVerificationGates.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/jwt/IdentityVerificationGates.kt new file mode 100644 index 0000000000..685777dafd --- /dev/null +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/jwt/IdentityVerificationGates.kt @@ -0,0 +1,52 @@ +package com.onesignal.user.internal.jwt + +import com.onesignal.debug.internal.logging.Logging + +/** + * Current Identity Verification gate state, pushed by `FeatureManager.applySideEffects`. + * + * Stored state is the tri-state [jwtRequirement] — UNKNOWN until the first HYDRATE so consumers + * that need to distinguish "we don't know yet" from "customer opted out" can. The Boolean + * derivations [ivBehaviorActive] and [newCodePathsRun] cover the common case where consumers + * just want yes/no; UNKNOWN reads as `false` for both, which is the safe default before HYDRATE. + * + * The two gates differ on purpose: [newCodePathsRun] also flips on when our SDK feature flag is + * on, honoring rollout state even when the customer hasn't opted in. [ivBehaviorActive] tracks + * customer config alone. + * + * Invariant `ivBehaviorActive == true ⇒ newCodePathsRun == true` is preserved at every + * observation because both are derived on read from the stored inputs; a reader can't observe an + * inconsistent state. + */ +internal object IdentityVerificationGates { + @Volatile + private var _featureFlagOn: Boolean = false + + /** Customer config (`jwt_required`); [JwtRequirement.UNKNOWN] until the first HYDRATE. */ + @Volatile + var jwtRequirement: JwtRequirement = JwtRequirement.UNKNOWN + private set + + /** Whether IV-specific behavior (JWT attachment, auth error handling) applies. UNKNOWN reads as `false`. */ + val ivBehaviorActive: Boolean + get() = jwtRequirement == JwtRequirement.REQUIRED + + /** Whether new IV-related code paths should run. `featureFlag_IV_ON || jwt_required == REQUIRED`. */ + val newCodePathsRun: Boolean + get() = _featureFlagOn || ivBehaviorActive + + /** Idempotent. [source] is logged for traceability. */ + fun update( + featureFlagOn: Boolean, + jwtRequirement: JwtRequirement, + source: String, + ) { + _featureFlagOn = featureFlagOn + this.jwtRequirement = jwtRequirement + + Logging.debug( + "OneSignal: IdentityVerificationGates.update: newCodePathsRun=$newCodePathsRun, " + + "ivBehaviorActive=$ivBehaviorActive (source=$source)", + ) + } +} diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/jwt/JwtRequirement.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/jwt/JwtRequirement.kt new file mode 100644 index 0000000000..3f21c4c0f4 --- /dev/null +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/jwt/JwtRequirement.kt @@ -0,0 +1,31 @@ +package com.onesignal.user.internal.jwt + +/** + * Customer-side JWT requirement, mirrored from the backend `jwt_required` remote param. + * Explicit [UNKNOWN] so callers can distinguish pre-HYDRATE (no value yet) from + * [NOT_REQUIRED] (customer opted out). + * + * Represents only the customer-config side of Identity Verification; do not confuse + * with [com.onesignal.core.internal.features.FeatureFlag.SDK_IDENTITY_VERIFICATION] (our + * SDK-side rollout switch). + */ +internal enum class JwtRequirement { + /** Remote params have not been fetched yet. Treat as non-IV until known. */ + UNKNOWN, + + /** Customer config `jwt_required=false`. No JWT signing. */ + NOT_REQUIRED, + + /** Customer config `jwt_required=true`. IV-specific behavior active. */ + REQUIRED, + ; + + companion object { + fun fromBoolean(value: Boolean?): JwtRequirement = + when (value) { + null -> UNKNOWN + false -> NOT_REQUIRED + true -> REQUIRED + } + } +} diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/jwt/JwtTokenStore.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/jwt/JwtTokenStore.kt new file mode 100644 index 0000000000..102db5e591 --- /dev/null +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/jwt/JwtTokenStore.kt @@ -0,0 +1,119 @@ +package com.onesignal.user.internal.jwt + +import com.onesignal.common.events.EventProducer +import com.onesignal.common.events.IEventNotifier +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.JSONException +import org.json.JSONObject + +/** + * Persistent store mapping externalId -> JWT. Multi-user so ops queued under a previous user + * can still resolve their JWT at execution time. Storage is unconditional; *usage* of JWTs is + * gated on [IdentityVerificationGates.ivBehaviorActive]. + */ +internal class JwtTokenStore( + private val _prefs: IPreferencesService, +) : IEventNotifier { + private val tokens: MutableMap = mutableMapOf() + private var isLoaded: Boolean = false + private val updates = EventProducer() + + override val hasSubscribers: Boolean + get() = updates.hasSubscribers + + override fun subscribe(handler: IJwtUpdateListener) = updates.subscribe(handler) + + override fun unsubscribe(handler: IJwtUpdateListener) = updates.unsubscribe(handler) + + fun getJwt(externalId: String): String? { + synchronized(tokens) { + ensureLoaded() + return tokens[externalId] + } + } + + /** Null [jwt] is a no-op; call [invalidateJwt] to remove a token. */ + fun putJwt( + externalId: String, + jwt: String?, + ) { + if (jwt == null) return + val changed: Boolean + synchronized(tokens) { + ensureLoaded() + changed = tokens[externalId] != jwt + tokens[externalId] = jwt + if (changed) { + persist() + } + } + if (changed) { + updates.fire { it.onJwtUpdated(externalId) } + } + } + + /** Removes the JWT for [externalId] and notifies subscribers. */ + fun invalidateJwt(externalId: String) { + val existed: Boolean + synchronized(tokens) { + ensureLoaded() + existed = tokens.remove(externalId) != null + if (existed) { + persist() + } + } + if (existed) { + updates.fire { it.onJwtUpdated(externalId) } + } + } + + /** Drops JWTs whose externalId isn't in [activeIds]. Call on cold start to bound growth. */ + fun pruneToExternalIds(activeIds: Set) { + val removed: Set + synchronized(tokens) { + ensureLoaded() + val toRemove = tokens.keys - activeIds + removed = toRemove.toSet() + if (removed.isNotEmpty()) { + tokens.keys.removeAll(removed) + persist() + } + } + for (externalId in removed) { + updates.fire { it.onJwtUpdated(externalId) } + } + } + + /** Caller 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: JSONException) { + Logging.warn("JwtTokenStore: failed to parse persisted tokens, starting fresh: ${e.message}") + } + } + isLoaded = true + } + + /** Caller must hold `synchronized(tokens)`. */ + private fun persist() { + _prefs.saveString( + PreferenceStores.ONESIGNAL, + PreferenceOneSignalKeys.PREFS_OS_JWT_TOKENS, + JSONObject(tokens.toMap()).toString(), + ) + } +} diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/CreateSubscriptionOperation.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/CreateSubscriptionOperation.kt index 35484f670d..c1335a1b25 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/CreateSubscriptionOperation.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/CreateSubscriptionOperation.kt @@ -88,9 +88,10 @@ class CreateSubscriptionOperation() : Operation(SubscriptionOperationExecutor.CR override val canStartExecute: Boolean get() = !IDManager.isLocalId(onesignalId) override val applyToRecordId: String get() = onesignalId - constructor(appId: String, onesignalId: String, subscriptionId: String, type: SubscriptionType, enabled: Boolean, address: String, status: SubscriptionStatus) : this() { + constructor(appId: String, onesignalId: String, externalId: String?, subscriptionId: String, type: SubscriptionType, enabled: Boolean, address: String, status: SubscriptionStatus) : this() { this.appId = appId this.onesignalId = onesignalId + this.externalId = externalId this.subscriptionId = subscriptionId this.type = type this.enabled = enabled diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/DeleteAliasOperation.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/DeleteAliasOperation.kt index 1595a6de2b..cfb1bf0bac 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/DeleteAliasOperation.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/DeleteAliasOperation.kt @@ -43,9 +43,10 @@ class DeleteAliasOperation() : Operation(IdentityOperationExecutor.DELETE_ALIAS) override val canStartExecute: Boolean get() = !IDManager.isLocalId(onesignalId) override val applyToRecordId: String get() = onesignalId - constructor(appId: String, onesignalId: String, label: String) : this() { + constructor(appId: String, onesignalId: String, externalId: String?, label: String) : this() { this.appId = appId this.onesignalId = onesignalId + this.externalId = externalId this.label = label } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/DeleteSubscriptionOperation.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/DeleteSubscriptionOperation.kt index 14c9aee448..8a5cc6bb52 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/DeleteSubscriptionOperation.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/DeleteSubscriptionOperation.kt @@ -44,9 +44,10 @@ class DeleteSubscriptionOperation() : Operation(SubscriptionOperationExecutor.DE override val canStartExecute: Boolean get() = !IDManager.isLocalId(onesignalId) && !IDManager.isLocalId(subscriptionId) override val applyToRecordId: String get() = subscriptionId - constructor(appId: String, onesignalId: String, subscriptionId: String) : this() { + constructor(appId: String, onesignalId: String, externalId: String?, subscriptionId: String) : this() { this.appId = appId this.onesignalId = onesignalId + this.externalId = externalId this.subscriptionId = subscriptionId } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/DeleteTagOperation.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/DeleteTagOperation.kt index f88ae3c568..4819c3248f 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/DeleteTagOperation.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/DeleteTagOperation.kt @@ -44,9 +44,10 @@ class DeleteTagOperation() : Operation(UpdateUserOperationExecutor.DELETE_TAG) { override val canStartExecute: Boolean get() = !IDManager.isLocalId(onesignalId) override val applyToRecordId: String get() = onesignalId - constructor(appId: String, onesignalId: String, key: String) : this() { + constructor(appId: String, onesignalId: String, externalId: String?, key: String) : this() { this.appId = appId this.onesignalId = onesignalId + this.externalId = externalId this.key = key } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/LoginUserFromSubscriptionOperation.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/LoginUserFromSubscriptionOperation.kt index 9597283f75..65b26765ce 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/LoginUserFromSubscriptionOperation.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/LoginUserFromSubscriptionOperation.kt @@ -28,7 +28,8 @@ class LoginUserFromSubscriptionOperation() : Operation(LoginUserFromSubscription } /** - * The optional external ID of this newly logged-in user. Must be unique for the [appId]. + * The subscription ID used to look up the user to log in as. Typically a v4 player ID being + * migrated to v5. */ var subscriptionId: String get() = getStringProperty(::subscriptionId.name) @@ -42,9 +43,10 @@ class LoginUserFromSubscriptionOperation() : Operation(LoginUserFromSubscription override val canStartExecute: Boolean = true override val applyToRecordId: String get() = subscriptionId - constructor(appId: String, onesignalId: String, subscriptionId: String) : this() { + constructor(appId: String, onesignalId: String, externalId: String?, subscriptionId: String) : this() { this.appId = appId this.onesignalId = onesignalId + this.externalId = externalId this.subscriptionId = subscriptionId } } 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 ca2f979450..54a20d03f6 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/RefreshUserOperation.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/RefreshUserOperation.kt index 953cbe7b9c..5f57cbac79 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/RefreshUserOperation.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/RefreshUserOperation.kt @@ -35,9 +35,10 @@ class RefreshUserOperation() : Operation(RefreshUserOperationExecutor.REFRESH_US override val canStartExecute: Boolean get() = !IDManager.isLocalId(onesignalId) override val applyToRecordId: String get() = onesignalId - constructor(appId: String, onesignalId: String) : this() { + constructor(appId: String, onesignalId: String, externalId: String?) : this() { this.appId = appId this.onesignalId = onesignalId + this.externalId = externalId } override fun translateIds(map: Map) { diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/SetAliasOperation.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/SetAliasOperation.kt index c88c23e46f..3bf5fd678b 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/SetAliasOperation.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/SetAliasOperation.kt @@ -53,9 +53,10 @@ class SetAliasOperation() : Operation(IdentityOperationExecutor.SET_ALIAS) { override val canStartExecute: Boolean get() = !IDManager.isLocalId(onesignalId) override val applyToRecordId: String get() = onesignalId - constructor(appId: String, onesignalId: String, label: String, value: String) : this() { + constructor(appId: String, onesignalId: String, externalId: String?, label: String, value: String) : this() { this.appId = appId this.onesignalId = onesignalId + this.externalId = externalId this.label = label this.value = value } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/SetPropertyOperation.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/SetPropertyOperation.kt index 2aff9c174a..0be614b9eb 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/SetPropertyOperation.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/SetPropertyOperation.kt @@ -52,9 +52,10 @@ class SetPropertyOperation() : Operation(UpdateUserOperationExecutor.SET_PROPERT override val canStartExecute: Boolean get() = !IDManager.isLocalId(onesignalId) override val applyToRecordId: String get() = onesignalId - constructor(appId: String, onesignalId: String, property: String, value: Any?) : this() { + constructor(appId: String, onesignalId: String, externalId: String?, property: String, value: Any?) : this() { this.appId = appId this.onesignalId = onesignalId + this.externalId = externalId this.property = property this.value = value } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/SetTagOperation.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/SetTagOperation.kt index 88bfa06eda..505b79506c 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/SetTagOperation.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/SetTagOperation.kt @@ -53,9 +53,10 @@ class SetTagOperation() : Operation(UpdateUserOperationExecutor.SET_TAG) { override val canStartExecute: Boolean get() = !IDManager.isLocalId(onesignalId) override val applyToRecordId: String get() = onesignalId - constructor(appId: String, onesignalId: String, key: String, value: String) : this() { + constructor(appId: String, onesignalId: String, externalId: String?, key: String, value: String) : this() { this.appId = appId this.onesignalId = onesignalId + this.externalId = externalId this.key = key this.value = value } 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/TrackPurchaseOperation.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/TrackPurchaseOperation.kt index 78da8cfb0a..7abb7170be 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/TrackPurchaseOperation.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/TrackPurchaseOperation.kt @@ -65,9 +65,10 @@ class TrackPurchaseOperation() : Operation(UpdateUserOperationExecutor.TRACK_PUR override val canStartExecute: Boolean get() = !IDManager.isLocalId(onesignalId) override val applyToRecordId: String get() = onesignalId - constructor(appId: String, onesignalId: String, treatNewAsExisting: Boolean, amountSpent: BigDecimal, purchases: List) : this() { + constructor(appId: String, onesignalId: String, externalId: String?, treatNewAsExisting: Boolean, amountSpent: BigDecimal, purchases: List) : this() { this.appId = appId this.onesignalId = onesignalId + this.externalId = externalId this.treatNewAsExisting = treatNewAsExisting this.amountSpent = amountSpent this.purchases = purchases diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/TrackSessionEndOperation.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/TrackSessionEndOperation.kt index 940051e91b..c0dfce68cc 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/TrackSessionEndOperation.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/TrackSessionEndOperation.kt @@ -43,9 +43,10 @@ class TrackSessionEndOperation() : Operation(UpdateUserOperationExecutor.TRACK_S override val canStartExecute: Boolean get() = !IDManager.isLocalId(onesignalId) override val applyToRecordId: String get() = onesignalId - constructor(appId: String, onesignalId: String, sessionTime: Long) : this() { + constructor(appId: String, onesignalId: String, externalId: String?, sessionTime: Long) : this() { this.appId = appId this.onesignalId = onesignalId + this.externalId = externalId this.sessionTime = sessionTime } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/TrackSessionStartOperation.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/TrackSessionStartOperation.kt index e5b9e0f29c..5b1285f5f7 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/TrackSessionStartOperation.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/TrackSessionStartOperation.kt @@ -34,9 +34,10 @@ class TrackSessionStartOperation() : Operation(UpdateUserOperationExecutor.TRACK override val canStartExecute: Boolean get() = !IDManager.isLocalId(onesignalId) override val applyToRecordId: String get() = onesignalId - constructor(appId: String, onesignalId: String) : this() { + constructor(appId: String, onesignalId: String, externalId: String?) : this() { this.appId = appId this.onesignalId = onesignalId + this.externalId = externalId } override fun translateIds(map: Map) { diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/TransferSubscriptionOperation.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/TransferSubscriptionOperation.kt index 54aa3bae27..c0d227260e 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/TransferSubscriptionOperation.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/TransferSubscriptionOperation.kt @@ -50,10 +50,11 @@ class TransferSubscriptionOperation() : Operation(SubscriptionOperationExecutor. override val canStartExecute: Boolean get() = !IDManager.isLocalId(onesignalId) && !IDManager.isLocalId(subscriptionId) override val applyToRecordId: String get() = subscriptionId - constructor(appId: String, subscriptionId: String, onesignalId: String) : this() { + constructor(appId: String, subscriptionId: String, onesignalId: String, externalId: String?) : this() { this.appId = appId this.subscriptionId = subscriptionId this.onesignalId = onesignalId + this.externalId = externalId } override fun translateIds(map: Map) { 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..51ea11282b 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 @@ -87,9 +87,10 @@ class UpdateSubscriptionOperation() : Operation(SubscriptionOperationExecutor.UP override val canStartExecute: Boolean get() = !IDManager.isLocalId(onesignalId) && !IDManager.isLocalId(subscriptionId) override val applyToRecordId: String get() = subscriptionId - constructor(appId: String, onesignalId: String, subscriptionId: String, type: SubscriptionType, enabled: Boolean, address: String, status: SubscriptionStatus) : this() { + constructor(appId: String, onesignalId: String, externalId: String?, subscriptionId: String, type: SubscriptionType, enabled: Boolean, address: String, status: SubscriptionStatus) : this() { this.appId = appId this.onesignalId = onesignalId + this.externalId = externalId this.subscriptionId = subscriptionId this.type = type this.enabled = enabled 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..2894f52631 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 @@ -74,7 +74,7 @@ internal class LoginUserFromSubscriptionOperationExecutor( return ExecutionResponse( ExecutionResult.SUCCESS, idTranslations, - listOf(RefreshUserOperation(loginUserOp.appId, backendOneSignalId)), + listOf(RefreshUserOperation(loginUserOp.appId, backendOneSignalId, loginUserOp.externalId)), ) } catch (ex: BackendException) { val responseType = NetworkUtils.getResponseStatusType(ex.statusCode) 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..1d68b9a26b 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 @@ -89,6 +89,7 @@ internal class LoginUserOperationExecutor( SetAliasOperation( loginUserOp.appId, loginUserOp.existingOnesignalId!!, + loginUserOp.externalId, IdentityConstants.EXTERNAL_ID, loginUserOp.externalId!!, ), @@ -223,7 +224,7 @@ internal class LoginUserOperationExecutor( val wasPossiblyAnUpsert = identities.isNotEmpty() val followUpOperations = if (wasPossiblyAnUpsert) { - listOf(RefreshUserOperation(createUserOperation.appId, backendOneSignalId)) + listOf(RefreshUserOperation(createUserOperation.appId, backendOneSignalId, createUserOperation.externalId)) } else { null } 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..e6c82f1a70 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 @@ -220,6 +220,7 @@ internal class SubscriptionOperationExecutor( CreateSubscriptionOperation( lastOperation.appId, lastOperation.onesignalId, + lastOperation.externalId, lastOperation.subscriptionId, lastOperation.type, lastOperation.enabled, 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..6a45235f1a 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 @@ -27,9 +27,9 @@ internal class IdentityModelStoreListener( newValue: Any?, ): Operation { return if (newValue != null && newValue is String) { - SetAliasOperation(_configModelStore.model.appId, model.onesignalId, property, newValue) + SetAliasOperation(_configModelStore.model.appId, model.onesignalId, model.externalId, property, newValue) } else { - DeleteAliasOperation(_configModelStore.model.appId, model.onesignalId, property) + DeleteAliasOperation(_configModelStore.model.appId, model.onesignalId, model.externalId, property) } } } 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..ca1b698df7 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,6 +15,7 @@ internal class PropertiesModelStoreListener( store: PropertiesModelStore, opRepo: IOperationRepo, private val _configModelStore: ConfigModelStore, + private val _identityModelStore: IdentityModelStore, ) : SingletonModelStoreListener(store, opRepo) { override fun getReplaceOperation(model: PropertiesModel): Operation? { // when the property model is replaced, nothing to do on the backend. Already handled via login process. @@ -36,14 +38,15 @@ internal class PropertiesModelStoreListener( return null } + val externalId = _identityModelStore.model.externalId if (path.startsWith(PropertiesModel::tags.name)) { return if (newValue != null && newValue is String) { - SetTagOperation(_configModelStore.model.appId, model.onesignalId, property, newValue) + SetTagOperation(_configModelStore.model.appId, model.onesignalId, externalId, property, newValue) } else { - DeleteTagOperation(_configModelStore.model.appId, model.onesignalId, property) + DeleteTagOperation(_configModelStore.model.appId, model.onesignalId, externalId, property) } } - return SetPropertyOperation(_configModelStore.model.appId, model.onesignalId, property, newValue) + return SetPropertyOperation(_configModelStore.model.appId, model.onesignalId, externalId, property, newValue) } } 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..a4af81c0f3 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 @@ -23,6 +23,7 @@ internal class SubscriptionModelStoreListener( return CreateSubscriptionOperation( _configModelStore.model.appId, _identityModelStore.model.onesignalId, + _identityModelStore.model.externalId, model.id, model.type, enabledAndStatus.first, @@ -32,7 +33,12 @@ internal class SubscriptionModelStoreListener( } override fun getRemoveOperation(model: SubscriptionModel): Operation { - return DeleteSubscriptionOperation(_configModelStore.model.appId, _identityModelStore.model.onesignalId, model.id) + return DeleteSubscriptionOperation( + _configModelStore.model.appId, + _identityModelStore.model.onesignalId, + _identityModelStore.model.externalId, + model.id, + ) } override fun getUpdateOperation( @@ -46,6 +52,7 @@ internal class SubscriptionModelStoreListener( return UpdateSubscriptionOperation( _configModelStore.model.appId, _identityModelStore.model.onesignalId, + _identityModelStore.model.externalId, model.id, model.type, enabledAndStatus.first, diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/service/UserRefreshService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/service/UserRefreshService.kt index 7b04d7981e..98e6f719db 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/service/UserRefreshService.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/service/UserRefreshService.kt @@ -32,6 +32,7 @@ class UserRefreshService( RefreshUserOperation( _configModelStore.model.appId, _identityModelStore.model.onesignalId, + _identityModelStore.model.externalId, ), ) } diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/features/FeatureFlagTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/features/FeatureFlagTests.kt index 54cf41b3a7..358e2ed424 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/features/FeatureFlagTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/features/FeatureFlagTests.kt @@ -15,4 +15,9 @@ class FeatureFlagTests : FunSpec({ test("SDK_BACKGROUND_THREADING uses the expected remote key") { FeatureFlag.SDK_BACKGROUND_THREADING.key shouldBe "sdk_background_threading" } + + test("SDK_IDENTITY_VERIFICATION uses the expected remote key and IMMEDIATE activation") { + FeatureFlag.SDK_IDENTITY_VERIFICATION.key shouldBe "sdk_identity_verification" + FeatureFlag.SDK_IDENTITY_VERIFICATION.activationMode shouldBe FeatureActivationMode.IMMEDIATE + } }) diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/features/FeatureManagerTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/features/FeatureManagerTests.kt index bc6695d419..9fc1d8301d 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/features/FeatureManagerTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/features/FeatureManagerTests.kt @@ -4,6 +4,8 @@ import com.onesignal.common.modeling.ModelChangeTags import com.onesignal.common.threading.ThreadingMode import com.onesignal.core.internal.config.ConfigModel import com.onesignal.core.internal.config.ConfigModelStore +import com.onesignal.user.internal.jwt.IdentityVerificationGates +import com.onesignal.user.internal.jwt.JwtRequirement import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import io.mockk.every @@ -15,11 +17,13 @@ import kotlinx.serialization.json.jsonPrimitive class FeatureManagerTests : FunSpec({ beforeEach { ThreadingMode.useBackgroundThreading = false + IdentityVerificationGates.update(false, JwtRequirement.UNKNOWN, "test-reset") } fun stubConfigModel(model: ConfigModel) { every { model.sdkRemoteFeatureFlags } returns emptyList() every { model.sdkRemoteFeatureFlagMetadata } returns null + every { model.useIdentityVerification } returns JwtRequirement.UNKNOWN } test("initial state enables BACKGROUND_THREADING when key is present in sdk remote flags") { @@ -188,4 +192,110 @@ class FeatureManagerTests : FunSpec({ manager.enabledFeatureKeys() shouldBe emptyList() } + + test("initial state: IDENTITY_VERIFICATION flag off + jwt_required=null → gates both false") { + val initialModel = mockk() + stubConfigModel(initialModel) + val configModelStore = mockk() + every { configModelStore.model } returns initialModel + every { configModelStore.subscribe(any()) } just runs + + val manager = FeatureManager(configModelStore) + + manager.isEnabled(FeatureFlag.SDK_IDENTITY_VERIFICATION) shouldBe false + IdentityVerificationGates.newCodePathsRun shouldBe false + IdentityVerificationGates.ivBehaviorActive shouldBe false + } + + test("initial state: IDENTITY_VERIFICATION flag on → newCodePathsRun=true, ivBehaviorActive=false") { + val initialModel = mockk() + stubConfigModel(initialModel) + every { initialModel.sdkRemoteFeatureFlags } returns listOf(FeatureFlag.SDK_IDENTITY_VERIFICATION.key) + val configModelStore = mockk() + every { configModelStore.model } returns initialModel + every { configModelStore.subscribe(any()) } just runs + + val manager = FeatureManager(configModelStore) + + manager.isEnabled(FeatureFlag.SDK_IDENTITY_VERIFICATION) shouldBe true + IdentityVerificationGates.newCodePathsRun shouldBe true + IdentityVerificationGates.ivBehaviorActive shouldBe false + } + + test("ERROR STATE: flag off + jwt_required=true → both gates true (customer config wins)") { + val initialModel = mockk() + stubConfigModel(initialModel) + every { initialModel.useIdentityVerification } returns JwtRequirement.REQUIRED + val configModelStore = mockk() + every { configModelStore.model } returns initialModel + every { configModelStore.subscribe(any()) } just runs + + val manager = FeatureManager(configModelStore) + + manager.isEnabled(FeatureFlag.SDK_IDENTITY_VERIFICATION) shouldBe false + IdentityVerificationGates.newCodePathsRun shouldBe true + IdentityVerificationGates.ivBehaviorActive shouldBe true + } + + test("initial state: flag on + jwt_required=true → full IV (both gates true)") { + val initialModel = mockk() + stubConfigModel(initialModel) + every { initialModel.sdkRemoteFeatureFlags } returns listOf(FeatureFlag.SDK_IDENTITY_VERIFICATION.key) + every { initialModel.useIdentityVerification } returns JwtRequirement.REQUIRED + val configModelStore = mockk() + every { configModelStore.model } returns initialModel + every { configModelStore.subscribe(any()) } just runs + + val manager = FeatureManager(configModelStore) + + manager.isEnabled(FeatureFlag.SDK_IDENTITY_VERIFICATION) shouldBe true + IdentityVerificationGates.newCodePathsRun shouldBe true + IdentityVerificationGates.ivBehaviorActive shouldBe true + } + + test("HYDRATE updates gates when useIdentityVerification arrives as true") { + val initialModel = mockk() + stubConfigModel(initialModel) + val configModelStore = mockk() + every { configModelStore.model } returns initialModel + every { configModelStore.subscribe(any()) } just runs + val manager = FeatureManager(configModelStore) + + IdentityVerificationGates.ivBehaviorActive shouldBe false + + val updatedModel = mockk() + stubConfigModel(updatedModel) + every { updatedModel.useIdentityVerification } returns JwtRequirement.REQUIRED + // Match production: store's model is swapped before onModelReplaced fires. + every { configModelStore.model } returns updatedModel + + manager.onModelReplaced(updatedModel, ModelChangeTags.HYDRATE) + + IdentityVerificationGates.newCodePathsRun shouldBe true + IdentityVerificationGates.ivBehaviorActive shouldBe true + } + + test("IDENTITY_VERIFICATION is IMMEDIATE: mid-session flag flip flows through to the gates") { + val initialModel = mockk() + stubConfigModel(initialModel) + val configModelStore = mockk() + every { configModelStore.model } returns initialModel + every { configModelStore.subscribe(any()) } just runs + val manager = FeatureManager(configModelStore) + + // Mid-session model replacement enables the flag remotely. + val updatedModel = mockk() + stubConfigModel(updatedModel) + every { updatedModel.sdkRemoteFeatureFlags } returns listOf(FeatureFlag.SDK_IDENTITY_VERIFICATION.key) + every { updatedModel.useIdentityVerification } returns JwtRequirement.NOT_REQUIRED + every { configModelStore.model } returns updatedModel + + manager.onModelReplaced(updatedModel, ModelChangeTags.HYDRATE) + + // Feature flag flips in-memory because IDENTITY_VERIFICATION is IMMEDIATE. + manager.isEnabled(FeatureFlag.SDK_IDENTITY_VERIFICATION) shouldBe true + // newCodePathsRun reflects the flipped flag; ivBehaviorActive still false (jwt_required=false). + IdentityVerificationGates.newCodePathsRun shouldBe true + IdentityVerificationGates.ivBehaviorActive shouldBe false + } }) diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/operations/OperationModelStoreTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/operations/OperationModelStoreTests.kt index d4fcee869d..644b4f4102 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/operations/OperationModelStoreTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/operations/OperationModelStoreTests.kt @@ -28,7 +28,7 @@ class OperationModelStoreTests : FunSpec({ val jsonArray = JSONArray() // 1. Create a VALID Operation with onesignalId - val validOperation = SetPropertyOperation(UUID.randomUUID().toString(), UUID.randomUUID().toString(), "property", "value") + val validOperation = SetPropertyOperation(UUID.randomUUID().toString(), UUID.randomUUID().toString(), null, "property", "value") validOperation.id = UUID.randomUUID().toString() // 2. Create a VALID operation missing onesignalId diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/jwt/IdentityVerificationGatesTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/jwt/IdentityVerificationGatesTests.kt new file mode 100644 index 0000000000..d185346ac7 --- /dev/null +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/jwt/IdentityVerificationGatesTests.kt @@ -0,0 +1,97 @@ +package com.onesignal.user.internal.jwt + +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe + +class IdentityVerificationGatesTests : FunSpec({ + // Singleton state leaks across tests; reset before each. + beforeEach { + IdentityVerificationGates.update(false, JwtRequirement.UNKNOWN, "test-reset") + } + + test("defaults to newCodePathsRun=false and ivBehaviorActive=false") { + IdentityVerificationGates.newCodePathsRun shouldBe false + IdentityVerificationGates.ivBehaviorActive shouldBe false + } + + test("featureFlagOn=false, jwtRequirement=UNKNOWN: both gates are false (safe default)") { + IdentityVerificationGates.update( + featureFlagOn = false, + jwtRequirement = JwtRequirement.UNKNOWN, + source = "test", + ) + IdentityVerificationGates.newCodePathsRun shouldBe false + IdentityVerificationGates.ivBehaviorActive shouldBe false + } + + test("featureFlagOn=false, jwtRequirement=NOT_REQUIRED: both gates are false") { + IdentityVerificationGates.update( + featureFlagOn = false, + jwtRequirement = JwtRequirement.NOT_REQUIRED, + source = "test", + ) + IdentityVerificationGates.newCodePathsRun shouldBe false + IdentityVerificationGates.ivBehaviorActive shouldBe false + } + + test("ERROR STATE — featureFlagOn=false, jwtRequirement=REQUIRED: both gates true (customer config wins)") { + IdentityVerificationGates.update( + featureFlagOn = false, + jwtRequirement = JwtRequirement.REQUIRED, + source = "test", + ) + IdentityVerificationGates.newCodePathsRun shouldBe true + IdentityVerificationGates.ivBehaviorActive shouldBe true + } + + test("featureFlagOn=true, jwtRequirement=UNKNOWN: newCodePathsRun true, ivBehaviorActive false") { + IdentityVerificationGates.update( + featureFlagOn = true, + jwtRequirement = JwtRequirement.UNKNOWN, + source = "test", + ) + IdentityVerificationGates.newCodePathsRun shouldBe true + IdentityVerificationGates.ivBehaviorActive shouldBe false + } + + test("featureFlagOn=true, jwtRequirement=NOT_REQUIRED: newCodePathsRun true, ivBehaviorActive false (Phase 3)") { + IdentityVerificationGates.update( + featureFlagOn = true, + jwtRequirement = JwtRequirement.NOT_REQUIRED, + source = "test", + ) + IdentityVerificationGates.newCodePathsRun shouldBe true + IdentityVerificationGates.ivBehaviorActive shouldBe false + } + + test("featureFlagOn=true, jwtRequirement=REQUIRED: both gates true (full IV)") { + IdentityVerificationGates.update( + featureFlagOn = true, + jwtRequirement = JwtRequirement.REQUIRED, + source = "test", + ) + IdentityVerificationGates.newCodePathsRun shouldBe true + IdentityVerificationGates.ivBehaviorActive shouldBe true + } + + test("updating to the same values is a no-op but still reflects in reads") { + IdentityVerificationGates.update(true, JwtRequirement.REQUIRED, "first") + IdentityVerificationGates.update(true, JwtRequirement.REQUIRED, "second") + IdentityVerificationGates.newCodePathsRun shouldBe true + IdentityVerificationGates.ivBehaviorActive shouldBe true + } + + test("transition: non-IV → IV-active → off") { + IdentityVerificationGates.update(false, JwtRequirement.NOT_REQUIRED, "phase-1-non-iv") + IdentityVerificationGates.newCodePathsRun shouldBe false + IdentityVerificationGates.ivBehaviorActive shouldBe false + + IdentityVerificationGates.update(true, JwtRequirement.REQUIRED, "phase-2-iv-on") + IdentityVerificationGates.newCodePathsRun shouldBe true + IdentityVerificationGates.ivBehaviorActive shouldBe true + + IdentityVerificationGates.update(false, JwtRequirement.NOT_REQUIRED, "kill-switch") + IdentityVerificationGates.newCodePathsRun shouldBe false + IdentityVerificationGates.ivBehaviorActive shouldBe false + } +}) diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/jwt/JwtTokenStoreTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/jwt/JwtTokenStoreTests.kt new file mode 100644 index 0000000000..664e502349 --- /dev/null +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/jwt/JwtTokenStoreTests.kt @@ -0,0 +1,231 @@ +package com.onesignal.user.internal.jwt + +import com.onesignal.core.internal.preferences.PreferenceOneSignalKeys +import com.onesignal.core.internal.preferences.PreferenceStores +import com.onesignal.debug.LogLevel +import com.onesignal.debug.internal.logging.Logging +import com.onesignal.mocks.MockPreferencesService +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import org.json.JSONObject + +class JwtTokenStoreTests : FunSpec({ + beforeEach { + // Silence logging to avoid android.util.Log.w not-mocked failures in Logging.warn + Logging.logLevel = LogLevel.NONE + } + + test("getJwt returns null for an externalId never stored") { + val store = JwtTokenStore(MockPreferencesService()) + + store.getJwt("alice") shouldBe null + } + + test("putJwt stores a token retrievable by externalId") { + val store = JwtTokenStore(MockPreferencesService()) + + store.putJwt("alice", "token-a") + + store.getJwt("alice") shouldBe "token-a" + } + + test("putJwt replaces an existing token for the same externalId") { + val store = JwtTokenStore(MockPreferencesService()) + store.putJwt("alice", "token-a1") + + store.putJwt("alice", "token-a2") + + store.getJwt("alice") shouldBe "token-a2" + } + + test("putJwt with null is a no-op (invalidate is the explicit path)") { + val store = JwtTokenStore(MockPreferencesService()) + store.putJwt("alice", "token-a") + + store.putJwt("alice", null) + + store.getJwt("alice") shouldBe "token-a" + } + + test("invalidateJwt removes the token for externalId") { + val store = JwtTokenStore(MockPreferencesService()) + store.putJwt("alice", "token-a") + + store.invalidateJwt("alice") + + store.getJwt("alice") shouldBe null + } + + test("invalidateJwt on an absent externalId is a no-op (no crash)") { + val store = JwtTokenStore(MockPreferencesService()) + + store.invalidateJwt("alice") + + store.getJwt("alice") shouldBe null + } + + test("putJwt persists to preferences and can be recovered by a fresh store instance") { + val prefs = MockPreferencesService() + val first = JwtTokenStore(prefs) + first.putJwt("alice", "token-a") + first.putJwt("bob", "token-b") + + val second = JwtTokenStore(prefs) + + second.getJwt("alice") shouldBe "token-a" + second.getJwt("bob") shouldBe "token-b" + } + + test("invalidateJwt persists so next launch does not see the token") { + val prefs = MockPreferencesService() + val first = JwtTokenStore(prefs) + first.putJwt("alice", "token-a") + first.invalidateJwt("alice") + + val second = JwtTokenStore(prefs) + + second.getJwt("alice") shouldBe null + } + + test("pruneToExternalIds removes tokens whose externalId is not in the active set") { + val store = JwtTokenStore(MockPreferencesService()) + store.putJwt("alice", "token-a") + store.putJwt("bob", "token-b") + store.putJwt("chris", "token-c") + + store.pruneToExternalIds(setOf("alice", "chris")) + + store.getJwt("alice") shouldBe "token-a" + store.getJwt("bob") shouldBe null + store.getJwt("chris") shouldBe "token-c" + } + + test("subscribers are notified when a new JWT is put") { + val store = JwtTokenStore(MockPreferencesService()) + val calls = mutableListOf() + store.subscribe( + object : IJwtUpdateListener { + override fun onJwtUpdated(externalId: String) { + calls.add(externalId) + } + }, + ) + + store.putJwt("alice", "token-a") + + calls shouldBe listOf("alice") + } + + test("subscribers are NOT notified when putJwt does not change the stored token") { + val store = JwtTokenStore(MockPreferencesService()) + store.putJwt("alice", "token-a") + val calls = mutableListOf() + store.subscribe( + object : IJwtUpdateListener { + override fun onJwtUpdated(externalId: String) { + calls.add(externalId) + } + }, + ) + + store.putJwt("alice", "token-a") + + calls.isEmpty() shouldBe true + } + + test("subscribers are notified on invalidation") { + val store = JwtTokenStore(MockPreferencesService()) + store.putJwt("alice", "token-a") + val calls = mutableListOf() + store.subscribe( + object : IJwtUpdateListener { + override fun onJwtUpdated(externalId: String) { + calls.add(externalId) + } + }, + ) + + store.invalidateJwt("alice") + + calls shouldBe listOf("alice") + } + + test("subscribers are NOT notified when invalidating a non-existent token") { + val store = JwtTokenStore(MockPreferencesService()) + val calls = mutableListOf() + store.subscribe( + object : IJwtUpdateListener { + override fun onJwtUpdated(externalId: String) { + calls.add(externalId) + } + }, + ) + + store.invalidateJwt("alice") + + calls.isEmpty() shouldBe true + } + + test("pruneToExternalIds fires for each removed externalId") { + val store = JwtTokenStore(MockPreferencesService()) + store.putJwt("alice", "token-a") + store.putJwt("bob", "token-b") + store.putJwt("chris", "token-c") + val calls = mutableListOf() + store.subscribe( + object : IJwtUpdateListener { + override fun onJwtUpdated(externalId: String) { + calls.add(externalId) + } + }, + ) + + store.pruneToExternalIds(setOf("alice")) + + // Order is not deterministic across JVMs; check set semantics. + calls.toSet() shouldBe setOf("bob", "chris") + } + + test("unsubscribed listener is not notified") { + val store = JwtTokenStore(MockPreferencesService()) + val calls = mutableListOf() + val listener = + object : IJwtUpdateListener { + override fun onJwtUpdated(externalId: String) { + calls.add(externalId) + } + } + store.subscribe(listener) + store.unsubscribe(listener) + + store.putJwt("alice", "token-a") + + calls.isEmpty() shouldBe true + } + + test("persisted JSON is the expected shape") { + val prefs = MockPreferencesService() + val store = JwtTokenStore(prefs) + + store.putJwt("alice", "token-a") + store.putJwt("bob", "token-b") + + val raw = prefs.getString(PreferenceStores.ONESIGNAL, PreferenceOneSignalKeys.PREFS_OS_JWT_TOKENS) + val obj = JSONObject(requireNotNull(raw)) + obj.getString("alice") shouldBe "token-a" + obj.getString("bob") shouldBe "token-b" + } + + test("malformed persisted JSON starts fresh without crashing") { + val prefs = + MockPreferencesService( + mapOf(PreferenceOneSignalKeys.PREFS_OS_JWT_TOKENS to "{not valid json"), + ) + val store = JwtTokenStore(prefs) + + store.getJwt("alice") shouldBe null + // Can still store new tokens after a malformed load + store.putJwt("alice", "token-a") + store.getJwt("alice") shouldBe "token-a" + } +}) 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..057ef2c96a 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 @@ -40,7 +40,7 @@ class IdentityOperationExecutorTests : FunSpec({ val identityOperationExecutor = IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState()) - val operations = listOf(SetAliasOperation("appId", "onesignalId", "aliasKey1", "aliasValue1")) + val operations = listOf(SetAliasOperation("appId", "onesignalId", null, "aliasKey1", "aliasValue1")) // When val response = identityOperationExecutor.execute(operations) @@ -70,7 +70,7 @@ class IdentityOperationExecutorTests : FunSpec({ val identityOperationExecutor = IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState()) - val operations = listOf(SetAliasOperation("appId", "onesignalId", "aliasKey1", "aliasValue1")) + val operations = listOf(SetAliasOperation("appId", "onesignalId", null, "aliasKey1", "aliasValue1")) // When @@ -91,7 +91,7 @@ class IdentityOperationExecutorTests : FunSpec({ val identityOperationExecutor = IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState()) - val operations = listOf(SetAliasOperation("appId", "onesignalId", "aliasKey1", "aliasValue1")) + val operations = listOf(SetAliasOperation("appId", "onesignalId", null, "aliasKey1", "aliasValue1")) // When @@ -112,7 +112,7 @@ class IdentityOperationExecutorTests : FunSpec({ val identityOperationExecutor = IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState()) - val operations = listOf(SetAliasOperation("appId", "onesignalId", "aliasKey1", "aliasValue1")) + val operations = listOf(SetAliasOperation("appId", "onesignalId", null, "aliasKey1", "aliasValue1")) // When @@ -135,7 +135,7 @@ class IdentityOperationExecutorTests : FunSpec({ val newRecordState = getNewRecordState(mockConfigModelStore).also { it.add("onesignalId") } val identityOperationExecutor = IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, newRecordState) - val operations = listOf(SetAliasOperation("appId", "onesignalId", "aliasKey1", "aliasValue1")) + val operations = listOf(SetAliasOperation("appId", "onesignalId", null, "aliasKey1", "aliasValue1")) // When @@ -161,7 +161,7 @@ class IdentityOperationExecutorTests : FunSpec({ val identityOperationExecutor = IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState()) - val operations = listOf(DeleteAliasOperation("appId", "onesignalId", "aliasKey1")) + val operations = listOf(DeleteAliasOperation("appId", "onesignalId", null, "aliasKey1")) // When val response = identityOperationExecutor.execute(operations) @@ -184,7 +184,7 @@ class IdentityOperationExecutorTests : FunSpec({ val identityOperationExecutor = IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState()) - val operations = listOf(DeleteAliasOperation("appId", "onesignalId", "aliasKey1")) + val operations = listOf(DeleteAliasOperation("appId", "onesignalId", null, "aliasKey1")) // When @@ -204,7 +204,7 @@ class IdentityOperationExecutorTests : FunSpec({ val identityOperationExecutor = IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState()) - val operations = listOf(DeleteAliasOperation("appId", "onesignalId", "aliasKey1")) + val operations = listOf(DeleteAliasOperation("appId", "onesignalId", null, "aliasKey1")) // When @@ -226,7 +226,7 @@ class IdentityOperationExecutorTests : FunSpec({ val identityOperationExecutor = IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, getNewRecordState()) - val operations = listOf(DeleteAliasOperation("appId", "onesignalId", "aliasKey1")) + val operations = listOf(DeleteAliasOperation("appId", "onesignalId", null, "aliasKey1")) // When @@ -251,7 +251,7 @@ class IdentityOperationExecutorTests : FunSpec({ val newRecordState = getNewRecordState(mockConfigModelStore).also { it.add("onesignalId") } val identityOperationExecutor = IdentityOperationExecutor(mockIdentityBackendService, mockIdentityModelStore, mockBuildUserService, newRecordState) - val operations = listOf(DeleteAliasOperation("appId", "onesignalId", "aliasKey1")) + val operations = listOf(DeleteAliasOperation("appId", "onesignalId", null, "aliasKey1")) // When val response = identityOperationExecutor.execute(operations) 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..e3c8c1cfb4 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 @@ -43,6 +43,7 @@ class LoginUserOperationExecutorTests : FunSpec({ CreateSubscriptionOperation( appId, localOneSignalId, + null, "subscriptionId1", SubscriptionType.PUSH, true, @@ -410,6 +411,7 @@ class LoginUserOperationExecutorTests : FunSpec({ CreateSubscriptionOperation( appId, localOneSignalId, + null, "subscriptionId1", SubscriptionType.PUSH, true, @@ -419,6 +421,7 @@ class LoginUserOperationExecutorTests : FunSpec({ UpdateSubscriptionOperation( appId, localOneSignalId, + null, "subscriptionId1", SubscriptionType.PUSH, true, @@ -428,13 +431,14 @@ class LoginUserOperationExecutorTests : FunSpec({ CreateSubscriptionOperation( appId, localOneSignalId, + null, "subscriptionId2", SubscriptionType.EMAIL, true, "name@company.com", SubscriptionStatus.SUBSCRIBED, ), - DeleteSubscriptionOperation(appId, localOneSignalId, "subscriptionId2"), + DeleteSubscriptionOperation(appId, localOneSignalId, null, "subscriptionId2"), ) // When @@ -511,6 +515,7 @@ class LoginUserOperationExecutorTests : FunSpec({ CreateSubscriptionOperation( appId, localOneSignalId, + null, localSubscriptionId1, SubscriptionType.PUSH, true, @@ -520,6 +525,7 @@ class LoginUserOperationExecutorTests : FunSpec({ CreateSubscriptionOperation( appId, localOneSignalId, + null, localSubscriptionId2, SubscriptionType.EMAIL, true, @@ -597,6 +603,7 @@ class LoginUserOperationExecutorTests : FunSpec({ CreateSubscriptionOperation( appId, localOneSignalId, + null, localSubscriptionId1, SubscriptionType.PUSH, true, @@ -606,6 +613,7 @@ class LoginUserOperationExecutorTests : FunSpec({ CreateSubscriptionOperation( appId, localOneSignalId, + null, localSubscriptionId2, SubscriptionType.EMAIL, true, @@ -669,6 +677,7 @@ class LoginUserOperationExecutorTests : FunSpec({ CreateSubscriptionOperation( appId, localOneSignalId, + null, localSubscriptionId1, SubscriptionType.PUSH, true, @@ -678,6 +687,7 @@ class LoginUserOperationExecutorTests : FunSpec({ CreateSubscriptionOperation( appId, localOneSignalId, + null, localSubscriptionId2, SubscriptionType.EMAIL, true, @@ -777,8 +787,8 @@ class LoginUserOperationExecutorTests : FunSpec({ val ops = listOf( LoginUserOperation(appId, localOneSignalId, null, null), - CreateSubscriptionOperation(appId, localOneSignalId, localSubscriptionId1, SubscriptionType.PUSH, true, "pushToken2", SubscriptionStatus.SUBSCRIBED), - CreateSubscriptionOperation(appId, localOneSignalId, localSubscriptionId2, SubscriptionType.EMAIL, true, "name@company.com", SubscriptionStatus.SUBSCRIBED), + CreateSubscriptionOperation(appId, localOneSignalId, null, localSubscriptionId1, SubscriptionType.PUSH, true, "pushToken2", SubscriptionStatus.SUBSCRIBED), + CreateSubscriptionOperation(appId, localOneSignalId, null, localSubscriptionId2, SubscriptionType.EMAIL, true, "name@company.com", SubscriptionStatus.SUBSCRIBED), ) // When @@ -840,7 +850,7 @@ class LoginUserOperationExecutorTests : FunSpec({ val ops = listOf( LoginUserOperation(appId, localOneSignalId, null, null), - CreateSubscriptionOperation(appId, localOneSignalId, localSubscriptionId1, SubscriptionType.PUSH, true, "pushToken1", SubscriptionStatus.SUBSCRIBED), + CreateSubscriptionOperation(appId, localOneSignalId, null, localSubscriptionId1, SubscriptionType.PUSH, true, "pushToken1", SubscriptionStatus.SUBSCRIBED), ) // When 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..0f3765c557 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 @@ -109,7 +109,7 @@ class RefreshUserOperationExecutorTests : FunSpec({ getNewRecordState(), ) - val operations = listOf(RefreshUserOperation(appId, remoteOneSignalId)) + val operations = listOf(RefreshUserOperation(appId, remoteOneSignalId, null)) try { // When @@ -193,7 +193,7 @@ class RefreshUserOperationExecutorTests : FunSpec({ getNewRecordState(), ) - val operations = listOf(RefreshUserOperation(appId, remoteOneSignalId)) + val operations = listOf(RefreshUserOperation(appId, remoteOneSignalId, null)) // When val response = refreshUserOperationExecutor.execute(operations) @@ -232,7 +232,7 @@ class RefreshUserOperationExecutorTests : FunSpec({ getNewRecordState(), ) - val operations = listOf(RefreshUserOperation(appId, remoteOneSignalId)) + val operations = listOf(RefreshUserOperation(appId, remoteOneSignalId, null)) // When val response = refreshUserOperationExecutor.execute(operations) @@ -267,7 +267,7 @@ class RefreshUserOperationExecutorTests : FunSpec({ getNewRecordState(), ) - val operations = listOf(RefreshUserOperation(appId, remoteOneSignalId)) + val operations = listOf(RefreshUserOperation(appId, remoteOneSignalId, null)) // When val response = refreshUserOperationExecutor.execute(operations) @@ -302,7 +302,7 @@ class RefreshUserOperationExecutorTests : FunSpec({ getNewRecordState(), ) - val operations = listOf(RefreshUserOperation(appId, remoteOneSignalId)) + val operations = listOf(RefreshUserOperation(appId, remoteOneSignalId, null)) // When val response = refreshUserOperationExecutor.execute(operations) @@ -339,7 +339,7 @@ class RefreshUserOperationExecutorTests : FunSpec({ newRecordState, ) - val operations = listOf(RefreshUserOperation(appId, remoteOneSignalId)) + val operations = listOf(RefreshUserOperation(appId, remoteOneSignalId, null)) // When val response = refreshUserOperationExecutor.execute(operations) 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..78f8ffc47f 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 @@ -75,6 +75,7 @@ class SubscriptionOperationExecutorTests : CreateSubscriptionOperation( appId, remoteOneSignalId, + null, localSubscriptionId, SubscriptionType.PUSH, true, @@ -135,6 +136,7 @@ class SubscriptionOperationExecutorTests : CreateSubscriptionOperation( appId, remoteOneSignalId, + null, remoteSubscriptionId, SubscriptionType.PUSH, true, @@ -185,6 +187,7 @@ class SubscriptionOperationExecutorTests : CreateSubscriptionOperation( appId, remoteOneSignalId, + null, localSubscriptionId, SubscriptionType.PUSH, true, @@ -240,6 +243,7 @@ class SubscriptionOperationExecutorTests : CreateSubscriptionOperation( appId, remoteOneSignalId, + null, localSubscriptionId, SubscriptionType.PUSH, true, @@ -295,6 +299,7 @@ class SubscriptionOperationExecutorTests : CreateSubscriptionOperation( appId, remoteOneSignalId, + null, localSubscriptionId, SubscriptionType.PUSH, true, @@ -338,13 +343,14 @@ class SubscriptionOperationExecutorTests : CreateSubscriptionOperation( appId, remoteOneSignalId, + null, localSubscriptionId, SubscriptionType.PUSH, true, "pushToken", SubscriptionStatus.SUBSCRIBED, ), - DeleteSubscriptionOperation(appId, remoteOneSignalId, localSubscriptionId), + DeleteSubscriptionOperation(appId, remoteOneSignalId, null, localSubscriptionId), ) // When @@ -384,6 +390,7 @@ class SubscriptionOperationExecutorTests : CreateSubscriptionOperation( appId, remoteOneSignalId, + null, localSubscriptionId, SubscriptionType.PUSH, true, @@ -393,6 +400,7 @@ class SubscriptionOperationExecutorTests : UpdateSubscriptionOperation( appId, remoteOneSignalId, + null, localSubscriptionId, SubscriptionType.PUSH, true, @@ -454,6 +462,7 @@ class SubscriptionOperationExecutorTests : UpdateSubscriptionOperation( appId, remoteOneSignalId, + null, remoteSubscriptionId, SubscriptionType.PUSH, true, @@ -463,6 +472,7 @@ class SubscriptionOperationExecutorTests : UpdateSubscriptionOperation( appId, remoteOneSignalId, + null, remoteSubscriptionId, SubscriptionType.PUSH, true, @@ -515,6 +525,7 @@ class SubscriptionOperationExecutorTests : UpdateSubscriptionOperation( appId, remoteOneSignalId, + null, remoteSubscriptionId, SubscriptionType.PUSH, true, @@ -567,6 +578,7 @@ class SubscriptionOperationExecutorTests : UpdateSubscriptionOperation( appId, remoteOneSignalId, + null, remoteSubscriptionId, SubscriptionType.PUSH, true, @@ -621,6 +633,7 @@ class SubscriptionOperationExecutorTests : UpdateSubscriptionOperation( appId, remoteOneSignalId, + null, remoteSubscriptionId, SubscriptionType.PUSH, true, @@ -660,7 +673,7 @@ class SubscriptionOperationExecutorTests : val operations = listOf( - DeleteSubscriptionOperation(appId, remoteOneSignalId, remoteSubscriptionId), + DeleteSubscriptionOperation(appId, remoteOneSignalId, null, remoteSubscriptionId), ) // When @@ -694,7 +707,7 @@ class SubscriptionOperationExecutorTests : val operations = listOf( - DeleteSubscriptionOperation(appId, remoteOneSignalId, remoteSubscriptionId), + DeleteSubscriptionOperation(appId, remoteOneSignalId, null, remoteSubscriptionId), ) // When @@ -729,7 +742,7 @@ class SubscriptionOperationExecutorTests : val operations = listOf( - DeleteSubscriptionOperation(appId, remoteOneSignalId, remoteSubscriptionId), + DeleteSubscriptionOperation(appId, remoteOneSignalId, null, remoteSubscriptionId), ) // When @@ -764,7 +777,7 @@ class SubscriptionOperationExecutorTests : val operations = listOf( - DeleteSubscriptionOperation(appId, remoteOneSignalId, remoteSubscriptionId), + DeleteSubscriptionOperation(appId, remoteOneSignalId, null, remoteSubscriptionId), ) // When @@ -808,6 +821,7 @@ class SubscriptionOperationExecutorTests : UpdateSubscriptionOperation( appId, remoteOneSignalId, + null, remoteSubscriptionId, SubscriptionType.PUSH, true, 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..2e0c122cbc 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 @@ -57,7 +57,7 @@ class UpdateUserOperationExecutorTests : getNewRecordState(), mockConsistencyManager, ) - val operations = listOf(SetTagOperation(appId, remoteOneSignalId, "tagKey1", "tagValue1")) + val operations = listOf(SetTagOperation(appId, remoteOneSignalId, null, "tagKey1", "tagValue1")) // When val response = loginUserOperationExecutor.execute(operations) @@ -99,21 +99,21 @@ class UpdateUserOperationExecutorTests : ) val operations = listOf( - SetTagOperation(appId, remoteOneSignalId, "tagKey1", "tagValue1-1"), - SetTagOperation(appId, remoteOneSignalId, "tagKey1", "tagValue1-2"), - SetTagOperation(appId, remoteOneSignalId, "tagKey2", "tagValue2"), - SetTagOperation(appId, remoteOneSignalId, "tagKey3", "tagValue3"), - DeleteTagOperation(appId, remoteOneSignalId, "tagKey3"), - SetPropertyOperation(appId, localOneSignalId, PropertiesModel::language.name, "lang1"), - SetPropertyOperation(appId, localOneSignalId, PropertiesModel::language.name, "lang2"), - SetPropertyOperation(appId, localOneSignalId, PropertiesModel::timezone.name, "timezone"), - SetPropertyOperation(appId, localOneSignalId, PropertiesModel::country.name, "country"), - SetPropertyOperation(appId, localOneSignalId, PropertiesModel::locationLatitude.name, 123.45), - SetPropertyOperation(appId, localOneSignalId, PropertiesModel::locationLongitude.name, 678.90), - SetPropertyOperation(appId, localOneSignalId, PropertiesModel::locationType.name, 1), - SetPropertyOperation(appId, localOneSignalId, PropertiesModel::locationAccuracy.name, 0.15), - SetPropertyOperation(appId, localOneSignalId, PropertiesModel::locationBackground.name, true), - SetPropertyOperation(appId, localOneSignalId, PropertiesModel::locationTimestamp.name, 1111L), + SetTagOperation(appId, remoteOneSignalId, null, "tagKey1", "tagValue1-1"), + SetTagOperation(appId, remoteOneSignalId, null, "tagKey1", "tagValue1-2"), + SetTagOperation(appId, remoteOneSignalId, null, "tagKey2", "tagValue2"), + SetTagOperation(appId, remoteOneSignalId, null, "tagKey3", "tagValue3"), + DeleteTagOperation(appId, remoteOneSignalId, null, "tagKey3"), + SetPropertyOperation(appId, localOneSignalId, null, PropertiesModel::language.name, "lang1"), + SetPropertyOperation(appId, localOneSignalId, null, PropertiesModel::language.name, "lang2"), + SetPropertyOperation(appId, localOneSignalId, null, PropertiesModel::timezone.name, "timezone"), + SetPropertyOperation(appId, localOneSignalId, null, PropertiesModel::country.name, "country"), + SetPropertyOperation(appId, localOneSignalId, null, PropertiesModel::locationLatitude.name, 123.45), + SetPropertyOperation(appId, localOneSignalId, null, PropertiesModel::locationLongitude.name, 678.90), + SetPropertyOperation(appId, localOneSignalId, null, PropertiesModel::locationType.name, 1), + SetPropertyOperation(appId, localOneSignalId, null, PropertiesModel::locationAccuracy.name, 0.15), + SetPropertyOperation(appId, localOneSignalId, null, PropertiesModel::locationBackground.name, true), + SetPropertyOperation(appId, localOneSignalId, null, PropertiesModel::locationTimestamp.name, 1111L), ) // When @@ -161,7 +161,7 @@ class UpdateUserOperationExecutorTests : ) val operations = listOf( - TrackSessionEndOperation(appId, remoteOneSignalId, 1111), + TrackSessionEndOperation(appId, remoteOneSignalId, null, 1111), ) // When @@ -206,10 +206,11 @@ class UpdateUserOperationExecutorTests : ) val operations = listOf( - TrackSessionEndOperation(appId, remoteOneSignalId, 1111), + TrackSessionEndOperation(appId, remoteOneSignalId, null, 1111), TrackPurchaseOperation( appId, remoteOneSignalId, + null, false, BigDecimal(2222), listOf( @@ -217,7 +218,7 @@ class UpdateUserOperationExecutorTests : PurchaseInfo("sku2", "iso2", BigDecimal(1222)), ), ), - TrackSessionEndOperation(appId, remoteOneSignalId, 3333), + TrackSessionEndOperation(appId, remoteOneSignalId, null, 3333), ) // When @@ -271,9 +272,9 @@ class UpdateUserOperationExecutorTests : ) val operations = listOf( - TrackSessionEndOperation(appId, remoteOneSignalId, 1111), - SetTagOperation(appId, remoteOneSignalId, "tagKey1", "tagValue1"), - TrackSessionEndOperation(appId, remoteOneSignalId, 3333), + TrackSessionEndOperation(appId, remoteOneSignalId, null, 1111), + SetTagOperation(appId, remoteOneSignalId, null, "tagKey1", "tagValue1"), + TrackSessionEndOperation(appId, remoteOneSignalId, null, 3333), ) // When @@ -317,7 +318,7 @@ class UpdateUserOperationExecutorTests : getNewRecordState(), mockConsistencyManager, ) - val operations = listOf(SetTagOperation(appId, remoteOneSignalId, "tagKey1", "tagValue1")) + val operations = listOf(SetTagOperation(appId, remoteOneSignalId, null, "tagKey1", "tagValue1")) // When val response = loginUserOperationExecutor.execute(operations) @@ -350,7 +351,7 @@ class UpdateUserOperationExecutorTests : newRecordState, mockConsistencyManager, ) - val operations = listOf(SetTagOperation(appId, remoteOneSignalId, "tagKey1", "tagValue1")) + val operations = listOf(SetTagOperation(appId, remoteOneSignalId, null, "tagKey1", "tagValue1")) // When val response = loginUserOperationExecutor.execute(operations) @@ -383,7 +384,7 @@ class UpdateUserOperationExecutorTests : val operations = listOf( - TrackSessionStartOperation(appId, onesignalId = remoteOneSignalId), + TrackSessionStartOperation(appId, remoteOneSignalId, null), ) // When diff --git a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/listeners/DeviceRegistrationListenerTests.kt b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/listeners/DeviceRegistrationListenerTests.kt index 188cc66cbd..3009177772 100644 --- a/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/listeners/DeviceRegistrationListenerTests.kt +++ b/OneSignalSDK/onesignal/notifications/src/test/java/com/onesignal/notifications/internal/listeners/DeviceRegistrationListenerTests.kt @@ -252,7 +252,7 @@ class DeviceRegistrationListenerTests : FunSpec({ permission = true, pushModel = uninitializedPushModel(), pushTokenResponse = - PushTokenResponse(NEW_TOKEN, SubscriptionStatus.SUBSCRIBED), + PushTokenResponse(NEW_TOKEN, SubscriptionStatus.SUBSCRIBED), ) // Permission flips off between gate evaluation and the IO callback. every { harness.notificationsManager.permission } returns true andThen false