Skip to content

Commit 17fc13b

Browse files
committed
Fix FAIL_UNAUTHORIZED loop when IV off and stuck login when IV arrives post-enqueue
OperationRepo: gate FAIL_UNAUTHORIZED re-queue on useIdentityVerification == true. When IV is OFF, hasValidJwtIfRequired() always returns true so re-queued ops were immediately eligible, creating a ~200ms infinite retry loop. Now IV-OFF treats FAIL_UNAUTHORIZED as FAIL_NORETRY (drop + wake waiters). OperationRepo: in removeOperationsWithoutExternalId(), clear local existingOnesignalId on queued LoginUserOperations. When IV=ON arrives via HYDRATE, anonymous CreateUserOperations are purged, orphaning the local ID that LoginUserOperation.canStartExecute was waiting on translateIds to resolve. Clearing it unblocks the operation and routes the executor through createUser(). LoginUserOperation: widen existingOnesignalId setter to internal. Fix Operation.externalId KDoc to reflect that subclass constructors set this field, not IOperationRepo at enqueue time. Made-with: Cursor
1 parent a292935 commit 17fc13b

File tree

3 files changed

+22
-4
lines changed

3 files changed

+22
-4
lines changed

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/Operation.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ abstract class Operation(name: String) : Model() {
1919
/**
2020
* The external ID of the user this operation belongs to. Used by [IOperationRepo] to look up
2121
* the correct JWT when identity verification is enabled, and to gate anonymous operations.
22-
* Stamped automatically by [IOperationRepo] at enqueue time from the current identity model
23-
* when not already set by the concrete operation's constructor.
22+
* Must be set by each concrete [Operation] subclass constructor — typically from the current
23+
* identity model's externalId at the time the operation is created.
2424
*/
2525
var externalId: String?
2626
get() = getOptStringProperty(::externalId.name)

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.onesignal.core.internal.operations.impl
22

3+
import com.onesignal.common.IDManager
34
import com.onesignal.common.threading.WaiterWithValue
45
import com.onesignal.core.internal.config.ConfigModelStore
56
import com.onesignal.core.internal.operations.ExecutionResult
@@ -13,6 +14,7 @@ import com.onesignal.debug.LogLevel
1314
import com.onesignal.debug.internal.logging.Logging
1415
import com.onesignal.user.internal.identity.IdentityModelStore
1516
import com.onesignal.user.internal.identity.JwtTokenStore
17+
import com.onesignal.user.internal.operations.LoginUserOperation
1618
import com.onesignal.user.internal.operations.impl.states.NewRecordsState
1719
import kotlinx.coroutines.CompletableDeferred
1820
import kotlinx.coroutines.CoroutineScope
@@ -284,8 +286,9 @@ internal class OperationRepo(
284286
ops.forEach { it.waiter?.wake(true) }
285287
}
286288
ExecutionResult.FAIL_UNAUTHORIZED -> {
289+
val identityVerificationEnabled = _configModelStore.model.useIdentityVerification == true
287290
val externalId = startingOp.operation.externalId
288-
if (externalId != null) {
291+
if (identityVerificationEnabled && externalId != null) {
289292
_jwtTokenStore.invalidateJwt(externalId)
290293
Logging.warn("Operation execution failed with 401 Unauthorized, JWT invalidated for user: $externalId. Operations re-queued.")
291294
// Unblock any enqueueAndWait callers so loginSuspend doesn't hang.
@@ -542,6 +545,21 @@ internal class OperationRepo(
542545
if (toRemove.isNotEmpty()) {
543546
Logging.debug("OperationRepo: removed ${toRemove.size} anonymous operations (no externalId)")
544547
}
548+
549+
// Any LoginUserOperation whose existingOnesignalId is a local ID was
550+
// waiting on a now-purged anonymous CreateUserOperation to translate it.
551+
// Clear it so canStartExecute unblocks and the executor takes the
552+
// createUser() path instead.
553+
queue.forEach {
554+
val op = it.operation
555+
if (op is LoginUserOperation) {
556+
val existing = op.existingOnesignalId
557+
if (existing != null && IDManager.isLocalId(existing)) {
558+
op.existingOnesignalId = null
559+
Logging.debug("OperationRepo: cleared local existingOnesignalId on LoginUserOperation (was $existing)")
560+
}
561+
}
562+
}
545563
}
546564
}
547565

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/LoginUserOperation.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class LoginUserOperation() : Operation(LoginUserOperationExecutor.LOGIN_USER) {
3939
*/
4040
var existingOnesignalId: String?
4141
get() = getOptStringProperty(::existingOnesignalId.name)
42-
private set(value) {
42+
internal set(value) {
4343
setOptStringProperty(::existingOnesignalId.name, value)
4444
}
4545

0 commit comments

Comments
 (0)