Skip to content

Commit c588498

Browse files
committed
Public API impl for JWT, User Manager, and callbacks
1 parent 3caffc9 commit c588498

File tree

7 files changed

+168
-6
lines changed

7 files changed

+168
-6
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: 55 additions & 2 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)
@@ -247,6 +252,7 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {
247252
// bootstrap services
248253
startupService.bootstrap()
249254

255+
resumeOperationRepoAfterFetchParams(configModel!!)
250256
if (forceCreateUser || !identityModelStore!!.model.hasProperty(IdentityConstants.ONESIGNAL_ID)) {
251257
val legacyPlayerId =
252258
preferencesService!!.getString(
@@ -284,7 +290,8 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {
284290
pushSubscriptionModel.id = legacyPlayerId
285291
pushSubscriptionModel.type = SubscriptionType.PUSH
286292
pushSubscriptionModel.optedIn =
287-
notificationTypes != SubscriptionStatus.NO_PERMISSION.value && notificationTypes != SubscriptionStatus.UNSUBSCRIBE.value
293+
notificationTypes != SubscriptionStatus.NO_PERMISSION.value &&
294+
notificationTypes != SubscriptionStatus.UNSUBSCRIBE.value
288295
pushSubscriptionModel.address =
289296
legacyUserSyncJSON.safeString("identifier") ?: ""
290297
if (notificationTypes != null) {
@@ -357,12 +364,16 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {
357364
currentIdentityOneSignalId = identityModelStore!!.model.onesignalId
358365

359366
if (currentIdentityExternalId == externalId) {
367+
// login is for same user that is already logged in, fetch (refresh)
368+
// the current user.
369+
identityModelStore!!.model.jwtToken = jwtBearerToken
360370
return
361371
}
362372

363373
// TODO: Set JWT Token for all future requests.
364374
createAndSwitchToNewUser { identityModel, _ ->
365375
identityModel.externalId = externalId
376+
identityModel.jwtToken = jwtBearerToken
366377
}
367378

368379
newIdentityOneSignalId = identityModelStore!!.model.onesignalId
@@ -405,6 +416,7 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {
405416
return
406417
}
407418

419+
// calling createAndSwitchToNewUser() replaces model with a default empty jwt
408420
createAndSwitchToNewUser()
409421
operationRepo!!.enqueue(
410422
LoginUserOperation(
@@ -413,9 +425,33 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {
413425
identityModelStore!!.model.externalId,
414426
),
415427
)
428+
}
429+
}
416430

417-
// TODO: remove JWT Token for all future requests.
431+
override fun updateUserJwt(
432+
externalId: String,
433+
token: String,
434+
) {
435+
// update the model with the given externalId
436+
for (model in identityModelStore!!.store.list()) {
437+
if (externalId == model.externalId) {
438+
identityModelStore!!.model.jwtToken = token
439+
operationRepo!!.setPaused(false)
440+
operationRepo!!.forceExecuteOperations()
441+
Logging.log(LogLevel.DEBUG, "JWT $token is updated for externalId $externalId")
442+
return
443+
}
418444
}
445+
446+
Logging.log(LogLevel.DEBUG, "No identity found for externalId $externalId")
447+
}
448+
449+
override fun addUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) {
450+
user.addUserJwtInvalidatedListener(listener)
451+
}
452+
453+
override fun removeUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) {
454+
user.removeUserJwtInvalidatedListener(listener)
419455
}
420456

421457
private fun createAndSwitchToNewUser(
@@ -483,6 +519,23 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {
483519
}
484520
}
485521

522+
private fun resumeOperationRepoAfterFetchParams(configModel: ConfigModel) {
523+
// pause operation repo until useIdentityVerification is determined
524+
operationRepo!!.setPaused(true)
525+
configModel.addFetchParamsObserver(
526+
object : FetchParamsObserver {
527+
override fun onParamsFetched(params: ParamsObject) {
528+
// resume operations if identity verification is turned off or a jwt is cached
529+
if (params.useIdentityVerification == false || identityModelStore!!.model.jwtToken != null) {
530+
operationRepo!!.setPaused(false)
531+
} else {
532+
Logging.log(LogLevel.ERROR, "A valid JWT is required for user ${identityModelStore!!.model.externalId}.")
533+
}
534+
}
535+
},
536+
)
537+
}
538+
486539
override fun <T> hasService(c: Class<T>): Boolean = services.hasService(c)
487540

488541
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)