Skip to content
11 changes: 7 additions & 4 deletions OneSignalSDK/detekt/detekt-baseline-core.xml
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@
<ID>ConstructorParameterNaming:UserBackendService.kt$UserBackendService$private val _httpClient: IHttpClient</ID>
<ID>ConstructorParameterNaming:UserManager.kt$UserManager$private val _customEventController: ICustomEventController</ID>
<ID>ConstructorParameterNaming:UserManager.kt$UserManager$private val _identityModelStore: IdentityModelStore</ID>
<ID>ConstructorParameterNaming:UserManager.kt$UserManager$private val _jwtTokenStore: JwtTokenStore</ID>

Check warning on line 158 in OneSignalSDK/detekt/detekt-baseline-core.xml

View check run for this annotation

Claude / Claude Code Review

Stale detekt baseline entries reference removed UserManager state

Two detekt baseline entries added/amended in this PR are now stale: line 158 suppresses `ConstructorParameterNaming` for `_jwtTokenStore: JwtTokenStore`, but UserManager.kt:26-32 has no such constructor parameter, and line 416 keys `TooManyFunctions` on `UserManager : IUserManagerISingletonModelStoreChangeHandlerIJwtUpdateListener`, but UserManager.kt:32 declares only `: IUserManager, ISingletonModelStoreChangeHandler<IdentityModel>` — `IJwtUpdateListener` is not implemented. Both entries suppre
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Two detekt baseline entries added/amended in this PR are now stale: line 158 suppresses ConstructorParameterNaming for _jwtTokenStore: JwtTokenStore, but UserManager.kt:26-32 has no such constructor parameter, and line 416 keys TooManyFunctions on UserManager : IUserManagerISingletonModelStoreChangeHandlerIJwtUpdateListener, but UserManager.kt:32 declares only : IUserManager, ISingletonModelStoreChangeHandler<IdentityModel>IJwtUpdateListener is not implemented. Both entries suppress non-existent warnings (created by an earlier version of this PR that was reverted by refactor commits 46d525f / 2a7edec which moved listener machinery into JwtTokenStore) and will silently mask any future legitimate detekt findings against UserManager. Fix: remove both lines, or regenerate the baseline.

Extended reasoning...

What the bug is

Two entries in OneSignalSDK/detekt/detekt-baseline-core.xml were added or amended by this PR but reference UserManager state that does not exist in the current code:

  1. Line 158 (added by this PR per the diff hunk + <ID>ConstructorParameterNaming:UserManager.kt$UserManager$private val _jwtTokenStore: JwtTokenStore</ID>) suppresses a ConstructorParameterNaming finding for a _jwtTokenStore: JwtTokenStore constructor parameter on UserManager.
  2. Line 416 (amended by this PR — diff shows -IUserManagerISingletonModelStoreChangeHandler+IUserManagerISingletonModelStoreChangeHandlerIJwtUpdateListener) suppresses a TooManyFunctions finding keyed on UserManager implementing IJwtUpdateListener.

Step-by-step proof

  1. Read UserManager.kt:26-32. The constructor signature is:
    internal open class UserManager(
        private val _subscriptionManager: ISubscriptionManager,
        private val _identityModelStore: IdentityModelStore,
        private val _propertiesModelStore: PropertiesModelStore,
        private val _customEventController: ICustomEventController,
        private val _languageContext: ILanguageContext,
    ) : IUserManager, ISingletonModelStoreChangeHandler<IdentityModel>
    There is no _jwtTokenStore: JwtTokenStore parameter, and the implemented-interface list is exactly IUserManager, ISingletonModelStoreChangeHandler<IdentityModel>IJwtUpdateListener is absent.
  2. grep for JwtTokenStore in UserManager.kt returns no matches.
  3. Detekt's baseline keys for ConstructorParameterNaming and TooManyFunctions include the constructor parameter list and the implemented-interface signature respectively. The keys at lines 158 and 416 do not match the class as it now exists, so the suppressions never apply.

Why this happened

This PR family went through several refactors. Earlier iterations injected JwtTokenStore into UserManager and made UserManager implement IJwtUpdateListener to bridge invalidation events to a UserManager-owned EventProducer<IUserJwtInvalidatedListener>. Subsequent commits — notably 46d525f ("refactor(iv): drop jwt-invalidated buffer/replay; pure pub/sub matches iOS") and 2a7edec ("refactor(iv): move IUserJwtInvalidatedListener notifier into JwtTokenStore") — moved the listener machinery directly into JwtTokenStore and removed both the constructor parameter and the interface implementation. The detekt baseline was not regenerated, so the suppressions added for the earlier shape remain.

Impact

No runtime impact — these are pure baseline-suppression entries. The cosmetic harm is real, though: stale entries silently mask any future legitimate detekt findings against UserManager's constructor naming or implemented-interface count. A future engineer adding a JWT-related parameter would see the warning suppressed without realizing why; a future refactor that legitimately bumps UserManager's function count past the threshold would have its TooManyFunctions warning auto-suppressed by a key that only happens to match an unrelated past shape. Detekt baselines exist to lock in known findings; stale entries undermine that contract.

Fix

Mechanical: delete the two stale lines from detekt-baseline-core.xml (line 158 and the IJwtUpdateListener portion of line 416, restoring it to UserManager : IUserManagerISingletonModelStoreChangeHandler if that finding still applies, or remove the line entirely if it does not). Alternatively, regenerate the baseline with ./gradlew detektBaseline and let detekt emit only the entries that match the current code.

<ID>ConstructorParameterNaming:UserManager.kt$UserManager$private val _languageContext: ILanguageContext</ID>
<ID>ConstructorParameterNaming:UserManager.kt$UserManager$private val _propertiesModelStore: PropertiesModelStore</ID>
<ID>ConstructorParameterNaming:UserManager.kt$UserManager$private val _subscriptionManager: ISubscriptionManager</ID>
Expand All @@ -173,7 +174,6 @@
<ID>ForbiddenComment:HttpClient.kt$HttpClient$// TODO: SHOULD RETURN OK INSTEAD OF NOT_MODIFIED TO MAKE TRANSPARENT?</ID>
<ID>ForbiddenComment:IPreferencesService.kt$PreferenceOneSignalKeys$* (String) The serialized IAMs TODO: This isn't currently used, determine if actually needed for cold start IAM fetch delay</ID>
<ID>ForbiddenComment:IUserBackendService.kt$IUserBackendService$// TODO: Change to send only the push subscription, optimally</ID>
<ID>ForbiddenComment:LoginHelper.kt$LoginHelper$// TODO: Set JWT Token for all future requests.</ID>
<ID>ForbiddenComment:LogoutHelper.kt$LogoutHelper$// TODO: remove JWT Token for all future requests.</ID>
<ID>ForbiddenComment:ParamsBackendService.kt$ParamsBackendService$// TODO: New</ID>
<ID>ForbiddenComment:PermissionsActivity.kt$PermissionsActivity$// TODO after we remove IAM from being an activity window we may be able to remove this handler</ID>
Expand Down Expand Up @@ -248,6 +248,7 @@
<ID>MagicNumber:OSDatabase.kt$OSDatabase$8</ID>
<ID>MagicNumber:OSDatabase.kt$OSDatabase$9</ID>
<ID>MagicNumber:OneSignalDispatchers.kt$OneSignalDispatchers$1024</ID>
<ID>MagicNumber:OneSignalImp.kt$OneSignalImp$8</ID>
<ID>MagicNumber:OperationRepo.kt$OperationRepo$1_000</ID>
<ID>MagicNumber:OutcomeEventsController.kt$OutcomeEventsController$1000</ID>
<ID>MagicNumber:PermissionsActivity.kt$PermissionsActivity$23</ID>
Expand Down Expand Up @@ -412,7 +413,7 @@
<ID>TooManyFunctions:OutcomeEventsController.kt$OutcomeEventsController : IOutcomeEventsControllerIStartableServiceISessionLifecycleHandler</ID>
<ID>TooManyFunctions:PreferencesService.kt$PreferencesService : IPreferencesServiceIStartableService</ID>
<ID>TooManyFunctions:SubscriptionManager.kt$SubscriptionManager : ISubscriptionManagerIModelStoreChangeHandlerISessionLifecycleHandler</ID>
<ID>TooManyFunctions:UserManager.kt$UserManager : IUserManagerISingletonModelStoreChangeHandler</ID>
<ID>TooManyFunctions:UserManager.kt$UserManager : IUserManagerISingletonModelStoreChangeHandlerIJwtUpdateListener</ID>
<ID>UndocumentedPublicClass:AlertDialogPrepromptForAndroidSettings.kt$AlertDialogPrepromptForAndroidSettings$Callback</ID>
<ID>UndocumentedPublicClass:AndroidUtils.kt$AndroidUtils</ID>
<ID>UndocumentedPublicClass:AndroidUtils.kt$AndroidUtils$SchemaType</ID>
Expand Down Expand Up @@ -457,7 +458,6 @@
<ID>UndocumentedPublicClass:JSONConverter.kt$JSONConverter</ID>
<ID>UndocumentedPublicClass:JSONUtils.kt$JSONUtils</ID>
<ID>UndocumentedPublicClass:Logging.kt$Logging</ID>
<ID>UndocumentedPublicClass:LoginHelper.kt$LoginHelper</ID>
<ID>UndocumentedPublicClass:LogoutHelper.kt$LogoutHelper</ID>
<ID>UndocumentedPublicClass:MigrationRecovery.kt$MigrationRecovery : IMigrationRecovery</ID>
<ID>UndocumentedPublicClass:NetworkUtils.kt$NetworkUtils</ID>
Expand Down Expand Up @@ -626,13 +626,16 @@
<ID>UnusedPrivateMember:AndroidUtils.kt$AndroidUtils$var requestPermission: String? = null</ID>
<ID>UnusedPrivateMember:ApplicationService.kt$ApplicationService$val listenerKey = "decorViewReady:$runnable"</ID>
<ID>UnusedPrivateMember:JSONUtils.kt$JSONUtils$`object`: Any</ID>
<ID>UnusedPrivateMember:LoginHelper.kt$LoginHelper$jwtBearerToken: String? = null</ID>
<ID>UnusedPrivateMember:OSDatabase.kt$OSDatabase.Companion$private const val FLOAT_TYPE = " FLOAT"</ID>
<ID>UnusedPrivateMember:OperationRepo.kt$OperationRepo$private val _time: ITime</ID>
<ID>UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("'initWithContext failed' before 'login'")</ID>
<ID>UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("'initWithContext failed' before 'logout'")</ID>
<ID>UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("'initWithContext failed' before 'updateUserJwt'")</ID>
<ID>UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("Must call 'initWithContext' before 'addUserJwtInvalidatedListener'")</ID>
<ID>UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("Must call 'initWithContext' before 'login'")</ID>
<ID>UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("Must call 'initWithContext' before 'logout'")</ID>
<ID>UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("Must call 'initWithContext' before 'removeUserJwtInvalidatedListener'")</ID>
<ID>UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("Must call 'initWithContext' before 'updateUserJwt'")</ID>
<ID>UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("Must call 'initWithContext' before use")</ID>
<ID>UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw initFailureException ?: IllegalStateException("Initialization failed. Cannot proceed.")</ID>
</CurrentIssues>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,34 @@ interface IOneSignal {
*/
fun logout()

/**
* Update the JWT bearer token associated with [externalId]. Use this when your backend
* has issued a new JWT for an already-logged-in user (e.g. in response to a previous
* [IUserJwtInvalidatedListener.onUserJwtInvalidated] callback). Stores the JWT and
* wakes the operation queue so any deferred ops can dispatch with the fresh token.
*
* @param externalId The external ID the JWT belongs to.
* @param token The new JWT bearer token issued by your backend.
*/
fun updateUserJwt(
externalId: String,
token: String,
)

/**
* Subscribe a listener for JWT-invalidated events. Fires on a background thread when
* the SDK detects that the stored JWT for a user is no longer valid (typically after
* a 401 from the OneSignal backend). Apps should respond by fetching a fresh JWT from
* their backend and supplying it via [updateUserJwt].
*
* Pure pub/sub: only listeners subscribed at the time of the invalidation receive the
* event. Subscribe early (e.g. in `Application.onCreate`) to avoid missing events.
*/
Comment thread
claude[bot] marked this conversation as resolved.
fun addUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener)

/** Unsubscribe a listener previously registered via [addUserJwtInvalidatedListener]. */
fun removeUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener)

Comment thread
claude[bot] marked this conversation as resolved.
// Suspend versions of property accessors and methods to avoid blocking threads

/**
Expand Down Expand Up @@ -226,4 +254,16 @@ interface IOneSignal {
* Logout the current user (suspend version).
*/
suspend fun logoutSuspend()

/**
* Update the JWT bearer token associated with [externalId] (suspend version). Suspends
* until SDK initialization is complete, then stores the JWT and wakes the operation queue.
*
* @param externalId The external ID the JWT belongs to.
* @param token The new JWT bearer token issued by your backend.
*/
suspend fun updateUserJwtSuspend(
externalId: String,
token: String,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.onesignal

/**
* Implement this interface and provide an instance to
* [IOneSignal.addUserJwtInvalidatedListener] to be notified when the SDK has
* detected that the JWT for a user is no longer valid (typically a 401 from
* the OneSignal backend on a request signed with that JWT).
*
* Threading: delivered on a background dispatcher
* (`OneSignalDispatchers.launchOnDefault`). Implementations should not assume a
* specific thread and should re-dispatch to the UI thread if needed.
*
* Pure pub/sub: only listeners subscribed at the time of the invalidation
* receive the event. Subscribe early (e.g. in `Application.onCreate`) to avoid
* missing cold-start 401s.
*/
fun interface IUserJwtInvalidatedListener {
/**
* Called when the JWT is invalidated for [UserJwtInvalidatedEvent.externalId].
* Apps should use this signal to fetch a fresh JWT from their backend and
* supply it via [IOneSignal.updateUserJwt].
*
* @param event Describes which user's JWT was invalidated.
*/
fun onUserJwtInvalidated(event: UserJwtInvalidatedEvent)
}
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,39 @@ object OneSignal {
@JvmStatic
fun logout() = oneSignal.logout()

/**
* Update the JWT bearer token associated with [externalId]. Use this when your backend
* has issued a new JWT for an already-logged-in user (e.g. in response to a previous
* [IUserJwtInvalidatedListener.onUserJwtInvalidated] callback). Stores the JWT and
* wakes the operation queue so any deferred ops can dispatch with the fresh token.
*
* @param externalId The external ID the JWT belongs to.
* @param token The new JWT bearer token issued by your backend.
*/
@JvmStatic
fun updateUserJwt(
externalId: String,
token: String,
) = oneSignal.updateUserJwt(externalId, token)

/**
* Subscribe a listener for JWT-invalidated events. Fires on a background thread when
* the SDK detects that the stored JWT for a user is no longer valid (typically after
* a 401 from the OneSignal backend). Apps should respond by fetching a fresh JWT from
* their backend and supplying it via [updateUserJwt].
*
* Pure pub/sub: only listeners subscribed at the time of the invalidation receive the
* event. Subscribe early (e.g. in `Application.onCreate`) to avoid missing events.
*/
@JvmStatic
fun addUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) =
oneSignal.addUserJwtInvalidatedListener(listener)

/** Unsubscribe a listener previously registered via [addUserJwtInvalidatedListener]. */
@JvmStatic
fun removeUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) =
oneSignal.removeUserJwtInvalidatedListener(listener)

private val oneSignal: IOneSignal by lazy {
OneSignalImp()
}
Expand Down Expand Up @@ -405,6 +438,21 @@ object OneSignal {
oneSignal.logoutSuspend()
}

/**
* Update the JWT bearer token associated with [externalId] without blocking the calling
* thread. Suspend-safe version of [updateUserJwt].
*
* @param externalId The external ID the JWT belongs to.
* @param token The new JWT bearer token issued by your backend.
*/
@JvmStatic
suspend fun updateUserJwtSuspend(
externalId: String,
token: String,
) {
oneSignal.updateUserJwtSuspend(externalId, token)
}

/**
* Used to retrieve services from the SDK when constructor dependency injection is not an
* option.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.onesignal

/**
* The event passed into [IUserJwtInvalidatedListener.onUserJwtInvalidated].
* Delivery occurs on a background thread.
*/
class UserJwtInvalidatedEvent(
val externalId: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@ internal fun OperationRepo.hasValidJwtIfRequired(

/**
* Handles a [com.onesignal.core.internal.operations.ExecutionResult.FAIL_UNAUTHORIZED] response
* when IV behavior is active. Invalidates the JWT for the failing op's externalId (which fires
* `IJwtUpdateListener.onJwtInvalidated` to subscribers, surfacing to the developer via the
* public-API layer), and re-queues the ops (waiter wake with `false` so `enqueueAndWait`
* when IV behavior is active. Invalidates the JWT for the failing op's externalId
* and re-queues the ops (waiter wake with `false` so `enqueueAndWait`
* callers don't hang).
*
* Returns `true` if IV-specific handling was applied (caller should stop processing this result),
Expand All @@ -48,8 +47,10 @@ internal fun OperationRepo.handleFailUnauthorized(
if (!ivBehaviorActive) return false
val externalId = startingOp.operation.externalId ?: return false

// Fires onJwtInvalidated to subscribers BEFORE we wake waiters — otherwise an
// `enqueueAndWait` caller could return before the developer-facing event propagates.
// Schedules an async fire of onUserJwtInvalidated to subscribers via
// OneSignalDispatchers.launchOnDefault — the developer-facing listener invocation is
// NOT ordered with respect to the waiter.wake below; awaiting `enqueueAndWait` callers
// may resume before, after, or concurrent with the listener.
jwtTokenStore.invalidateJwt(externalId)
Logging.info(
"Operation execution failed with 401 Unauthorized, JWT invalidated for user: $externalId. " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.onesignal.internal

import android.content.Context
import com.onesignal.IOneSignal
import com.onesignal.IUserJwtInvalidatedListener
import com.onesignal.common.AndroidUtils
import com.onesignal.common.DeviceUtils
import com.onesignal.common.OneSignalUtils
Expand Down Expand Up @@ -37,6 +38,7 @@ import com.onesignal.user.internal.LoginHelper
import com.onesignal.user.internal.LogoutHelper
import com.onesignal.user.internal.UserSwitcher
import com.onesignal.user.internal.identity.IdentityModelStore
import com.onesignal.user.internal.jwt.JwtTokenStore
import com.onesignal.user.internal.properties.PropertiesModelStore
import com.onesignal.user.internal.resolveAppId
import com.onesignal.user.internal.subscriptions.SubscriptionModelStore
Expand Down Expand Up @@ -142,6 +144,7 @@ internal class OneSignalImp(
private val propertiesModelStore: PropertiesModelStore by lazy { services.getService<PropertiesModelStore>() }
private val subscriptionModelStore: SubscriptionModelStore by lazy { services.getService<SubscriptionModelStore>() }
private val preferencesService: IPreferencesService by lazy { services.getService<IPreferencesService>() }
private val jwtTokenStore: JwtTokenStore by lazy { services.getService<JwtTokenStore>() }
private val listOfModules =
listOf(
"com.onesignal.notifications.NotificationsModule",
Expand Down Expand Up @@ -220,6 +223,7 @@ internal class OneSignalImp(
userSwitcher = userSwitcher,
operationRepo = operationRepo,
configModel = configModel,
jwtTokenStore = jwtTokenStore,
lock = loginLogoutLock,
)
}
Expand Down Expand Up @@ -381,7 +385,7 @@ internal class OneSignalImp(
externalId: String,
jwtBearerToken: String?,
) {
Logging.log(LogLevel.DEBUG, "Calling deprecated login(externalId: $externalId, jwtBearerToken: $jwtBearerToken)")
Logging.log(LogLevel.DEBUG, "Calling deprecated login(externalId: $externalId, jwtBearerToken: ...${jwtBearerToken?.takeLast(8)})")

if (isBackgroundThreadingEnabled) {
waitForInit(operationName = "login")
Expand Down Expand Up @@ -428,6 +432,47 @@ internal class OneSignalImp(
}
}

override fun updateUserJwt(
externalId: String,
Comment thread
claude[bot] marked this conversation as resolved.
token: String,
) {
Logging.log(LogLevel.DEBUG, "updateUserJwt(externalId: $externalId, token: ...${token.takeLast(8)})")

if (isBackgroundThreadingEnabled) {
waitForInit(operationName = "updateUserJwt")
} else {
if (!isInitialized) {
throw IllegalStateException("Must call 'initWithContext' before 'updateUserJwt'")
}
}

jwtTokenStore.putJwt(externalId, token)
// Wake the queue so any deferred ops can dispatch with the fresh token.
operationRepo.forceExecuteOperations()
}

override fun addUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) {
if (isBackgroundThreadingEnabled) {
waitForInit(operationName = "addUserJwtInvalidatedListener")
} else {
if (!isInitialized) {
throw IllegalStateException("Must call 'initWithContext' before 'addUserJwtInvalidatedListener'")
}
}
jwtTokenStore.addUserJwtInvalidatedListener(listener)
}

override fun removeUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) {
if (isBackgroundThreadingEnabled) {
waitForInit(operationName = "removeUserJwtInvalidatedListener")
} else {
if (!isInitialized) {
throw IllegalStateException("Must call 'initWithContext' before 'removeUserJwtInvalidatedListener'")
}
}
jwtTokenStore.removeUserJwtInvalidatedListener(listener)
}
Comment thread
claude[bot] marked this conversation as resolved.

override fun <T> hasService(c: Class<T>): Boolean = services.hasService(c)

override fun <T> getService(c: Class<T>): T = services.getService(c)
Expand Down Expand Up @@ -657,7 +702,7 @@ internal class OneSignalImp(
externalId: String,
jwtBearerToken: String?,
) = withContext(runtimeIoDispatcher) {
Logging.log(LogLevel.DEBUG, "login(externalId: $externalId, jwtBearerToken: $jwtBearerToken)")
Logging.log(LogLevel.DEBUG, "login(externalId: $externalId, jwtBearerToken: ...${jwtBearerToken?.takeLast(8)})")

suspendUntilInit(operationName = "login")

Expand All @@ -669,6 +714,22 @@ internal class OneSignalImp(
loginHelper.enqueueLogin(context)
}

override suspend fun updateUserJwtSuspend(
externalId: String,
token: String,
) = withContext(runtimeIoDispatcher) {
Logging.log(LogLevel.DEBUG, "updateUserJwtSuspend(externalId: $externalId, token: ...${token.takeLast(8)})")

suspendUntilInit(operationName = "updateUserJwt")

if (!isInitialized) {
throw IllegalStateException("'initWithContext failed' before 'updateUserJwt'")
}

jwtTokenStore.putJwt(externalId, token)
operationRepo.forceExecuteOperations()
}

override suspend fun logoutSuspend() =
withContext(runtimeIoDispatcher) {
Logging.log(LogLevel.DEBUG, "logoutSuspend()")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ internal class UserModule : IModule {
builder.register<LoginUserOperationExecutor>().provides<IOperationExecutor>()
builder.register<LoginUserFromSubscriptionOperationExecutor>().provides<IOperationExecutor>()
builder.register<RefreshUserOperationExecutor>().provides<IOperationExecutor>()
builder.register<UserManager>().provides<IUserManager>()
builder.register<UserManager>()
.provides<IUserManager>()
.provides<UserManager>()
Comment thread
claude[bot] marked this conversation as resolved.
builder.register<CustomEventController>().provides<ICustomEventController>()
builder.register<CustomEventOperationExecutor>().provides<IOperationExecutor>()
builder.register<CustomEventBackendService>().provides<ICustomEventBackendService>()
Expand Down
Loading
Loading