Skip to content

Commit c7cad83

Browse files
committed
Public API impl for JWT, User Manager, and callbacks
1 parent e6959ff commit c7cad83

File tree

7 files changed

+169
-7
lines changed

7 files changed

+169
-7
lines changed

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ interface IOneSignal {
1919
*/
2020
val isInitialized: Boolean
2121

22+
/**
23+
* Whether the security feature to authenticate your external user ids is enabled
24+
*/
25+
val useIdentityVerification: Boolean
26+
2227
/**
2328
* The user manager for accessing user-scoped
2429
* management.
@@ -123,4 +128,16 @@ interface IOneSignal {
123128
* data is not cleared.
124129
*/
125130
fun logout()
131+
132+
/**
133+
* Update JWT token for a user
134+
*/
135+
fun updateUserJwt(
136+
externalId: String,
137+
token: String,
138+
)
139+
140+
fun addUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener)
141+
142+
fun removeUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener)
126143
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.onesignal
2+
3+
/** TODO: complete the comment part for this listener
4+
* Implement this interface and provide an instance to [OneSignal.addUserJwtInvalidatedListner]
5+
* in order to receive control when the JWT for the current user is invalidated.
6+
*
7+
* @see [User JWT Invalidated Event | OneSignal Docs](https://documentation.onesignal.com/docs/)
8+
*/
9+
interface IUserJwtInvalidatedListener {
10+
/**
11+
* Called when the JWT is invalidated
12+
*
13+
* @param event The user JWT that expired.
14+
*/
15+
fun onUserJwtInvalidated(event: UserJwtInvalidatedEvent)
16+
}

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ object OneSignal {
2929
val isInitialized: Boolean
3030
get() = oneSignal.isInitialized
3131

32+
/**
33+
* Whether the security feature to authenticate your external user ids is enabled
34+
*/
35+
@JvmStatic
36+
val useIdentityVerification: Boolean
37+
get() = oneSignal.useIdentityVerification
38+
3239
/**
3340
* The current SDK version as a string.
3441
*/
@@ -192,6 +199,24 @@ object OneSignal {
192199
@JvmStatic
193200
fun logout() = oneSignal.logout()
194201

202+
@JvmStatic
203+
fun updateUserJwt(
204+
externalId: String,
205+
token: String,
206+
) {
207+
oneSignal.updateUserJwt(externalId, token)
208+
}
209+
210+
@JvmStatic
211+
fun addUserJwtInvalidatedListner(listener: IUserJwtInvalidatedListener) {
212+
oneSignal.addUserJwtInvalidatedListener(listener)
213+
}
214+
215+
@JvmStatic
216+
fun removeUserJwtInvalidatedListner(listener: IUserJwtInvalidatedListener) {
217+
oneSignal.removeUserJwtInvalidatedListener(listener)
218+
}
219+
195220
private val oneSignal: IOneSignal by lazy {
196221
OneSignalImp()
197222
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.onesignal
2+
3+
/** TODO: jwt documentation
4+
* The event passed into [IUserJwtInvalidatedListener.onUserJwtInvalidated], it provides access
5+
* to the external ID whose JWT has just been invalidated.
6+
*
7+
*/
8+
class UserJwtInvalidatedEvent(
9+
val externalId: String,
10+
)

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

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.onesignal.internal
33
import android.content.Context
44
import android.os.Build
55
import com.onesignal.IOneSignal
6+
import com.onesignal.IUserJwtInvalidatedListener
67
import com.onesignal.common.AndroidUtils
78
import com.onesignal.common.DeviceUtils
89
import com.onesignal.common.IDManager
@@ -18,8 +19,10 @@ import com.onesignal.common.threading.suspendifyOnThread
1819
import com.onesignal.core.CoreModule
1920
import com.onesignal.core.internal.application.IApplicationService
2021
import com.onesignal.core.internal.application.impl.ApplicationService
22+
import com.onesignal.core.internal.backend.ParamsObject
2123
import com.onesignal.core.internal.config.ConfigModel
2224
import com.onesignal.core.internal.config.ConfigModelStore
25+
import com.onesignal.core.internal.config.FetchParamsObserver
2326
import com.onesignal.core.internal.operations.IOperationRepo
2427
import com.onesignal.core.internal.preferences.IPreferencesService
2528
import com.onesignal.core.internal.preferences.PreferenceOneSignalKeys
@@ -56,6 +59,8 @@ import org.json.JSONObject
5659
internal class OneSignalImp : IOneSignal, IServiceProvider {
5760
override val sdkVersion: String = OneSignalUtils.SDK_VERSION
5861
override var isInitialized: Boolean = false
62+
override val useIdentityVerification: Boolean
63+
get() = configModel?.useIdentityVerification ?: true
5964

6065
override var consentRequired: Boolean
6166
get() = configModel?.consentRequired ?: (_consentRequired == true)
@@ -255,6 +260,7 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {
255260
// bootstrap services
256261
startupService.bootstrap()
257262

263+
resumeOperationRepoAfterFetchParams(configModel!!)
258264
if (forceCreateUser || !identityModelStore!!.model.hasProperty(IdentityConstants.ONESIGNAL_ID)) {
259265
val legacyPlayerId =
260266
preferencesService!!.getString(
@@ -292,7 +298,8 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {
292298
pushSubscriptionModel.id = legacyPlayerId
293299
pushSubscriptionModel.type = SubscriptionType.PUSH
294300
pushSubscriptionModel.optedIn =
295-
notificationTypes != SubscriptionStatus.NO_PERMISSION.value && notificationTypes != SubscriptionStatus.UNSUBSCRIBE.value
301+
notificationTypes != SubscriptionStatus.NO_PERMISSION.value &&
302+
notificationTypes != SubscriptionStatus.UNSUBSCRIBE.value
296303
pushSubscriptionModel.address =
297304
legacyUserSyncJSON.safeString("identifier") ?: ""
298305
if (notificationTypes != null) {
@@ -365,12 +372,16 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {
365372
currentIdentityOneSignalId = identityModelStore!!.model.onesignalId
366373

367374
if (currentIdentityExternalId == externalId) {
375+
// login is for same user that is already logged in, fetch (refresh)
376+
// the current user.
377+
identityModelStore!!.model.jwtToken = jwtBearerToken
368378
return
369379
}
370380

371381
// TODO: Set JWT Token for all future requests.
372382
createAndSwitchToNewUser { identityModel, _ ->
373383
identityModel.externalId = externalId
384+
identityModel.jwtToken = jwtBearerToken
374385
}
375386

376387
newIdentityOneSignalId = identityModelStore!!.model.onesignalId
@@ -413,6 +424,7 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {
413424
return
414425
}
415426

427+
// calling createAndSwitchToNewUser() replaces model with a default empty jwt
416428
createAndSwitchToNewUser()
417429
operationRepo!!.enqueue(
418430
LoginUserOperation(
@@ -421,8 +433,6 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {
421433
identityModelStore!!.model.externalId,
422434
),
423435
)
424-
425-
// TODO: remove JWT Token for all future requests.
426436
}
427437
}
428438

@@ -435,6 +445,32 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {
435445
PreferenceOneSignalKeys.PREFS_LEGACY_APP_ID,
436446
)
437447
}
448+
449+
override fun updateUserJwt(
450+
externalId: String,
451+
token: String,
452+
) {
453+
// update the model with the given externalId
454+
for (model in identityModelStore!!.store.list()) {
455+
if (externalId == model.externalId) {
456+
identityModelStore!!.model.jwtToken = token
457+
operationRepo!!.setPaused(false)
458+
operationRepo!!.forceExecuteOperations()
459+
Logging.log(LogLevel.DEBUG, "JWT $token is updated for externalId $externalId")
460+
return
461+
}
462+
}
463+
464+
Logging.log(LogLevel.DEBUG, "No identity found for externalId $externalId")
465+
}
466+
467+
override fun addUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) {
468+
user.addUserJwtInvalidatedListener(listener)
469+
}
470+
471+
override fun removeUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) {
472+
user.removeUserJwtInvalidatedListener(listener)
473+
}
438474

439475
private fun createAndSwitchToNewUser(
440476
suppressBackendOperation: Boolean = false,
@@ -501,6 +537,23 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {
501537
}
502538
}
503539

540+
private fun resumeOperationRepoAfterFetchParams(configModel: ConfigModel) {
541+
// pause operation repo until useIdentityVerification is determined
542+
operationRepo!!.setPaused(true)
543+
configModel.addFetchParamsObserver(
544+
object : FetchParamsObserver {
545+
override fun onParamsFetched(params: ParamsObject) {
546+
// resume operations if identity verification is turned off or a jwt is cached
547+
if (params.useIdentityVerification == false || identityModelStore!!.model.jwtToken != null) {
548+
operationRepo!!.setPaused(false)
549+
} else {
550+
Logging.log(LogLevel.ERROR, "A valid JWT is required for user ${identityModelStore!!.model.externalId}.")
551+
}
552+
}
553+
},
554+
)
555+
}
556+
504557
override fun <T> hasService(c: Class<T>): Boolean = services.hasService(c)
505558

506559
override fun <T> getService(c: Class<T>): T = services.getService(c)

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/IUserManager.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.onesignal.user
22

3+
import com.onesignal.IUserJwtInvalidatedListener
34
import com.onesignal.OneSignal
45
import com.onesignal.user.state.IUserStateObserver
56
import com.onesignal.user.subscriptions.IPushSubscription
@@ -166,4 +167,11 @@ interface IUserManager {
166167
* Remove an observer from the user state.
167168
*/
168169
fun removeObserver(observer: IUserStateObserver)
170+
171+
/**
172+
* Add an event listener allowing user to be notified when the JWT is invalidated.
173+
*/
174+
fun addUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener)
175+
176+
fun removeUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener)
169177
}

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

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.onesignal.user.internal
22

3+
import com.onesignal.IUserJwtInvalidatedListener
4+
import com.onesignal.OneSignal
5+
import com.onesignal.UserJwtInvalidatedEvent
36
import com.onesignal.common.IDManager
47
import com.onesignal.common.OneSignalUtils
58
import com.onesignal.common.events.EventProducer
@@ -41,6 +44,10 @@ internal open class UserManager(
4144

4245
val changeHandlersNotifier = EventProducer<IUserStateObserver>()
4346

47+
val jwtInvalidatedCallback = EventProducer<IUserJwtInvalidatedListener>()
48+
49+
private var jwtTokenInvalidated: String? = null
50+
4451
override val pushSubscription: IPushSubscription
4552
get() = _subscriptionManager.subscriptions.push
4653

@@ -244,6 +251,16 @@ internal open class UserManager(
244251
changeHandlersNotifier.unsubscribe(observer)
245252
}
246253

254+
override fun addUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) {
255+
Logging.debug("OneSignal.addClickListener(listener: $listener)")
256+
jwtInvalidatedCallback.subscribe(listener)
257+
}
258+
259+
override fun removeUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) {
260+
Logging.debug("OneSignal.removeClickListener(listener: $listener)")
261+
jwtInvalidatedCallback.unsubscribe(listener)
262+
}
263+
247264
override fun onModelReplaced(
248265
model: IdentityModel,
249266
tag: String,
@@ -253,10 +270,26 @@ internal open class UserManager(
253270
args: ModelChangedArgs,
254271
tag: String,
255272
) {
256-
if (args.property == IdentityConstants.ONESIGNAL_ID) {
257-
val newUserState = UserState(args.newValue.toString(), externalId)
258-
this.changeHandlersNotifier.fire {
259-
it.onUserStateChange(UserChangedState(newUserState))
273+
when (args.property) {
274+
IdentityConstants.ONESIGNAL_ID -> {
275+
val newUserState = UserState(args.newValue.toString(), externalId)
276+
this.changeHandlersNotifier.fire {
277+
it.onUserStateChange(UserChangedState(newUserState))
278+
}
279+
}
280+
IdentityConstants.JWT_TOKEN -> {
281+
// Fire the event when the JWT has been invalidated.
282+
val oldJwt = args.oldValue.toString()
283+
val newJwt = args.newValue.toString()
284+
285+
// prevent same JWT from being invalidated twice in a row
286+
if (OneSignal.useIdentityVerification && jwtTokenInvalidated != oldJwt && newJwt.isEmpty()) {
287+
jwtInvalidatedCallback.fire {
288+
it.onUserJwtInvalidated(UserJwtInvalidatedEvent((externalId)))
289+
}
290+
}
291+
292+
jwtTokenInvalidated = oldJwt
260293
}
261294
}
262295
}

0 commit comments

Comments
 (0)