Skip to content

Commit ee27ce4

Browse files
nan-liclaude
andcommitted
fix(iv): expose new public methods on OneSignal facade + log hygiene
Three review-driven fixes on the IV public-API surface: 1. OneSignal.kt: add @JvmStatic wrappers for updateUserJwt, addUserJwtInvalidatedListener, removeUserJwtInvalidatedListener, and updateUserJwtSuspend. Without these, the four new IOneSignal methods were unreachable from app code (the OneSignal object is the documented Java/Kotlin entry point and "implements IOneSignal in spirit"). Mirrors the convention from reference branch #2599. 2. OneSignalImp.kt: mask jwtBearerToken in login()/loginSuspend() DEBUG logs (...${jwtBearerToken?.takeLast(8)}). Pre-PR the parameter was a no-op (LoginHelper had a TODO) so the leak was theoretical; this PR wires the token through to JwtTokenStore.putJwt, so a live bearer credential now flows through Logging.log at DEBUG. updateUserJwt already masks; mirror that. 3. UserManager.kt: restore Logging.warn(msg, ex) form on the two new runCatching.onFailure handlers in fireJwtInvalidated and addJwtInvalidatedListener. Interpolating ${ex.message} drops the stack trace; Logging.warn already accepts a Throwable second arg that propagates through to log listeners and Otel — same pattern restored in JwtTokenStore via e76fb60. Also refreshes detekt baseline for the new MagicNumber (takeLast(8)), ConstructorParameterNaming (_jwtTokenStore on UserManager), TooManyFunctions (UserManager now also implements IJwtUpdateListener), and UseCheckOrError on the 4 new IllegalStateException throws. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent cbb01af commit ee27ce4

4 files changed

Lines changed: 60 additions & 8 deletions

File tree

OneSignalSDK/detekt/detekt-baseline-core.xml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@
155155
<ID>ConstructorParameterNaming:UserBackendService.kt$UserBackendService$private val _httpClient: IHttpClient</ID>
156156
<ID>ConstructorParameterNaming:UserManager.kt$UserManager$private val _customEventController: ICustomEventController</ID>
157157
<ID>ConstructorParameterNaming:UserManager.kt$UserManager$private val _identityModelStore: IdentityModelStore</ID>
158+
<ID>ConstructorParameterNaming:UserManager.kt$UserManager$private val _jwtTokenStore: JwtTokenStore</ID>
158159
<ID>ConstructorParameterNaming:UserManager.kt$UserManager$private val _languageContext: ILanguageContext</ID>
159160
<ID>ConstructorParameterNaming:UserManager.kt$UserManager$private val _propertiesModelStore: PropertiesModelStore</ID>
160161
<ID>ConstructorParameterNaming:UserManager.kt$UserManager$private val _subscriptionManager: ISubscriptionManager</ID>
@@ -173,7 +174,6 @@
173174
<ID>ForbiddenComment:HttpClient.kt$HttpClient$// TODO: SHOULD RETURN OK INSTEAD OF NOT_MODIFIED TO MAKE TRANSPARENT?</ID>
174175
<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>
175176
<ID>ForbiddenComment:IUserBackendService.kt$IUserBackendService$// TODO: Change to send only the push subscription, optimally</ID>
176-
<ID>ForbiddenComment:LoginHelper.kt$LoginHelper$// TODO: Set JWT Token for all future requests.</ID>
177177
<ID>ForbiddenComment:LogoutHelper.kt$LogoutHelper$// TODO: remove JWT Token for all future requests.</ID>
178178
<ID>ForbiddenComment:ParamsBackendService.kt$ParamsBackendService$// TODO: New</ID>
179179
<ID>ForbiddenComment:PermissionsActivity.kt$PermissionsActivity$// TODO after we remove IAM from being an activity window we may be able to remove this handler</ID>
@@ -248,6 +248,7 @@
248248
<ID>MagicNumber:OSDatabase.kt$OSDatabase$8</ID>
249249
<ID>MagicNumber:OSDatabase.kt$OSDatabase$9</ID>
250250
<ID>MagicNumber:OneSignalDispatchers.kt$OneSignalDispatchers$1024</ID>
251+
<ID>MagicNumber:OneSignalImp.kt$OneSignalImp$8</ID>
251252
<ID>MagicNumber:OperationRepo.kt$OperationRepo$1_000</ID>
252253
<ID>MagicNumber:OutcomeEventsController.kt$OutcomeEventsController$1000</ID>
253254
<ID>MagicNumber:PermissionsActivity.kt$PermissionsActivity$23</ID>
@@ -412,7 +413,7 @@
412413
<ID>TooManyFunctions:OutcomeEventsController.kt$OutcomeEventsController : IOutcomeEventsControllerIStartableServiceISessionLifecycleHandler</ID>
413414
<ID>TooManyFunctions:PreferencesService.kt$PreferencesService : IPreferencesServiceIStartableService</ID>
414415
<ID>TooManyFunctions:SubscriptionManager.kt$SubscriptionManager : ISubscriptionManagerIModelStoreChangeHandlerISessionLifecycleHandler</ID>
415-
<ID>TooManyFunctions:UserManager.kt$UserManager : IUserManagerISingletonModelStoreChangeHandler</ID>
416+
<ID>TooManyFunctions:UserManager.kt$UserManager : IUserManagerISingletonModelStoreChangeHandlerIJwtUpdateListener</ID>
416417
<ID>UndocumentedPublicClass:AlertDialogPrepromptForAndroidSettings.kt$AlertDialogPrepromptForAndroidSettings$Callback</ID>
417418
<ID>UndocumentedPublicClass:AndroidUtils.kt$AndroidUtils</ID>
418419
<ID>UndocumentedPublicClass:AndroidUtils.kt$AndroidUtils$SchemaType</ID>
@@ -457,7 +458,6 @@
457458
<ID>UndocumentedPublicClass:JSONConverter.kt$JSONConverter</ID>
458459
<ID>UndocumentedPublicClass:JSONUtils.kt$JSONUtils</ID>
459460
<ID>UndocumentedPublicClass:Logging.kt$Logging</ID>
460-
<ID>UndocumentedPublicClass:LoginHelper.kt$LoginHelper</ID>
461461
<ID>UndocumentedPublicClass:LogoutHelper.kt$LogoutHelper</ID>
462462
<ID>UndocumentedPublicClass:MigrationRecovery.kt$MigrationRecovery : IMigrationRecovery</ID>
463463
<ID>UndocumentedPublicClass:NetworkUtils.kt$NetworkUtils</ID>
@@ -626,13 +626,16 @@
626626
<ID>UnusedPrivateMember:AndroidUtils.kt$AndroidUtils$var requestPermission: String? = null</ID>
627627
<ID>UnusedPrivateMember:ApplicationService.kt$ApplicationService$val listenerKey = "decorViewReady:$runnable"</ID>
628628
<ID>UnusedPrivateMember:JSONUtils.kt$JSONUtils$`object`: Any</ID>
629-
<ID>UnusedPrivateMember:LoginHelper.kt$LoginHelper$jwtBearerToken: String? = null</ID>
630629
<ID>UnusedPrivateMember:OSDatabase.kt$OSDatabase.Companion$private const val FLOAT_TYPE = " FLOAT"</ID>
631630
<ID>UnusedPrivateMember:OperationRepo.kt$OperationRepo$private val _time: ITime</ID>
632631
<ID>UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("'initWithContext failed' before 'login'")</ID>
633632
<ID>UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("'initWithContext failed' before 'logout'")</ID>
633+
<ID>UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("'initWithContext failed' before 'updateUserJwt'")</ID>
634+
<ID>UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("Must call 'initWithContext' before 'addUserJwtInvalidatedListener'")</ID>
634635
<ID>UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("Must call 'initWithContext' before 'login'")</ID>
635636
<ID>UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("Must call 'initWithContext' before 'logout'")</ID>
637+
<ID>UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("Must call 'initWithContext' before 'removeUserJwtInvalidatedListener'")</ID>
638+
<ID>UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("Must call 'initWithContext' before 'updateUserJwt'")</ID>
636639
<ID>UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw IllegalStateException("Must call 'initWithContext' before use")</ID>
637640
<ID>UseCheckOrError:OneSignalImp.kt$OneSignalImp$throw initFailureException ?: IllegalStateException("Initialization failed. Cannot proceed.")</ID>
638641
</CurrentIssues>

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/OneSignal.kt

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,40 @@ object OneSignal {
343343
@JvmStatic
344344
fun logout() = oneSignal.logout()
345345

346+
/**
347+
* Update the JWT bearer token associated with [externalId]. Use this when your backend
348+
* has issued a new JWT for an already-logged-in user (e.g. in response to a previous
349+
* [IUserJwtInvalidatedListener.onUserJwtInvalidated] callback). Stores the JWT and
350+
* wakes the operation queue so any deferred ops can dispatch with the fresh token.
351+
*
352+
* @param externalId The external ID the JWT belongs to.
353+
* @param token The new JWT bearer token issued by your backend.
354+
*/
355+
@JvmStatic
356+
fun updateUserJwt(
357+
externalId: String,
358+
token: String,
359+
) = oneSignal.updateUserJwt(externalId, token)
360+
361+
/**
362+
* Subscribe a listener for JWT-invalidated events. Fires on a background thread when
363+
* the SDK detects that the stored JWT for a user is no longer valid (typically after
364+
* a 401 from the OneSignal backend). Apps should respond by fetching a fresh JWT from
365+
* their backend and supplying it via [updateUserJwt].
366+
*
367+
* Listener replay: if an invalidation has already occurred before this listener is
368+
* registered, the most recent invalidation is delivered to the new listener so apps
369+
* that subscribe late don't miss the signal.
370+
*/
371+
@JvmStatic
372+
fun addUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) =
373+
oneSignal.addUserJwtInvalidatedListener(listener)
374+
375+
/** Unsubscribe a listener previously registered via [addUserJwtInvalidatedListener]. */
376+
@JvmStatic
377+
fun removeUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) =
378+
oneSignal.removeUserJwtInvalidatedListener(listener)
379+
346380
private val oneSignal: IOneSignal by lazy {
347381
OneSignalImp()
348382
}
@@ -405,6 +439,21 @@ object OneSignal {
405439
oneSignal.logoutSuspend()
406440
}
407441

442+
/**
443+
* Update the JWT bearer token associated with [externalId] without blocking the calling
444+
* thread. Suspend-safe version of [updateUserJwt].
445+
*
446+
* @param externalId The external ID the JWT belongs to.
447+
* @param token The new JWT bearer token issued by your backend.
448+
*/
449+
@JvmStatic
450+
suspend fun updateUserJwtSuspend(
451+
externalId: String,
452+
token: String,
453+
) {
454+
oneSignal.updateUserJwtSuspend(externalId, token)
455+
}
456+
408457
/**
409458
* Used to retrieve services from the SDK when constructor dependency injection is not an
410459
* option.

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OneSignalImp.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ internal class OneSignalImp(
386386
externalId: String,
387387
jwtBearerToken: String?,
388388
) {
389-
Logging.log(LogLevel.DEBUG, "Calling deprecated login(externalId: $externalId, jwtBearerToken: $jwtBearerToken)")
389+
Logging.log(LogLevel.DEBUG, "Calling deprecated login(externalId: $externalId, jwtBearerToken: ...${jwtBearerToken?.takeLast(8)})")
390390

391391
if (isBackgroundThreadingEnabled) {
392392
waitForInit(operationName = "login")
@@ -703,7 +703,7 @@ internal class OneSignalImp(
703703
externalId: String,
704704
jwtBearerToken: String?,
705705
) = withContext(runtimeIoDispatcher) {
706-
Logging.log(LogLevel.DEBUG, "login(externalId: $externalId, jwtBearerToken: $jwtBearerToken)")
706+
Logging.log(LogLevel.DEBUG, "login(externalId: $externalId, jwtBearerToken: ...${jwtBearerToken?.takeLast(8)})")
707707

708708
suspendUntilInit(operationName = "login")
709709

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ internal open class UserManager(
108108
// subscribe/unsubscribe/fire calls. Replay runs on the caller's thread (sync).
109109
pendingExternalId?.let {
110110
runCatching { listener.onUserJwtInvalidated(UserJwtInvalidatedEvent(it)) }
111-
.onFailure { ex -> Logging.warn("UserManager: replayed jwt-invalidated listener threw: ${ex.message}") }
111+
.onFailure { ex -> Logging.warn("UserManager: replayed jwt-invalidated listener threw", ex) }
112112
}
113113
}
114114

@@ -127,7 +127,7 @@ internal open class UserManager(
127127
jwtInvalidatedDispatchScope.launch {
128128
jwtInvalidatedNotifier.fire { listener ->
129129
runCatching { listener.onUserJwtInvalidated(UserJwtInvalidatedEvent(externalId)) }
130-
.onFailure { ex -> Logging.warn("UserManager: jwt-invalidated listener threw: ${ex.message}") }
130+
.onFailure { ex -> Logging.warn("UserManager: jwt-invalidated listener threw", ex) }
131131
}
132132
}
133133
} else {

0 commit comments

Comments
 (0)