Skip to content

Commit

Permalink
Merge pull request #219 from stytchauth/jordan/fix-offline
Browse files Browse the repository at this point in the history
Move to lazy delegates with custom getter to avoid race conditions with SharedPreferences
  • Loading branch information
jhaven-stytch authored Oct 9, 2024
2 parents 7e1c1e8 + 66c99d7 commit 8652609
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 142 deletions.
2 changes: 1 addition & 1 deletion source/sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ plugins {

ext {
PUBLISH_GROUP_ID = 'com.stytch.sdk'
PUBLISH_VERSION = '0.29.0'
PUBLISH_VERSION = '0.29.1'
PUBLISH_ARTIFACT_ID = 'sdk'
}

Expand Down
120 changes: 41 additions & 79 deletions source/sdk/src/main/java/com/stytch/sdk/b2b/StytchB2BClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import com.stytch.sdk.common.QUERY_TOKEN_TYPE
import com.stytch.sdk.common.StorageHelper
import com.stytch.sdk.common.StytchClientOptions
import com.stytch.sdk.common.StytchDispatchers
import com.stytch.sdk.common.StytchLazyDelegate
import com.stytch.sdk.common.StytchResult
import com.stytch.sdk.common.dfp.ActivityProvider
import com.stytch.sdk.common.dfp.CaptchaProviderImpl
Expand Down Expand Up @@ -83,7 +84,7 @@ import java.util.UUID
public object StytchB2BClient {
internal var dispatchers: StytchDispatchers = StytchDispatchers()
internal var externalScope: CoroutineScope = GlobalScope // TODO: SDK-614
internal val sessionStorage = B2BSessionStorage(StorageHelper, externalScope)
internal lateinit var sessionStorage: B2BSessionStorage
internal var pkcePairManager: PKCEPairManager = PKCEPairManagerImpl(StorageHelper, EncryptionManager)
internal lateinit var dfpProvider: DFPProvider
internal var bootstrapData: BootstrapData = BootstrapData()
Expand Down Expand Up @@ -126,6 +127,7 @@ public object StytchB2BClient {
deviceInfo = context.getDeviceInfo()
appSessionId = "app-session-id-${UUID.randomUUID()}"
StorageHelper.initialize(context)
sessionStorage = B2BSessionStorage(StorageHelper, externalScope)
StytchB2BApi.configure(publicToken, deviceInfo)
val activityProvider = ActivityProvider(context.applicationContext as Application)
dfpProvider =
Expand Down Expand Up @@ -251,7 +253,7 @@ public object StytchB2BClient {
}

internal fun assertInitialized() {
if (!StytchB2BApi.isInitialized) {
if (!StytchB2BApi.isInitialized || !::sessionStorage.isInitialized) {
throw StytchSDKNotConfiguredError("StytchB2BClient")
}
}
Expand All @@ -264,7 +266,7 @@ public object StytchB2BClient {
* StytchB2BClient.configure()
*/
@JvmStatic
public val magicLinks: B2BMagicLinks =
public val magicLinks: B2BMagicLinks by StytchLazyDelegate(::assertInitialized) {
B2BMagicLinksImpl(
externalScope,
dispatchers,
Expand All @@ -273,10 +275,7 @@ public object StytchB2BClient {
StytchB2BApi.MagicLinks.Discovery,
pkcePairManager,
)
get() {
assertInitialized()
return field
}
}

/**
* Exposes an instance of the [B2BSessions] interface which provides methods for authenticating, updating, or
Expand All @@ -286,17 +285,14 @@ public object StytchB2BClient {
* StytchB2BClient.configure()
*/
@JvmStatic
public val sessions: B2BSessions =
public val sessions: B2BSessions by StytchLazyDelegate(::assertInitialized) {
B2BSessionsImpl(
externalScope,
dispatchers,
sessionStorage,
StytchB2BApi.Sessions,
)
get() {
assertInitialized()
return field
}
}

/**
* Exposes an instance of the [Organization] interface which provides methods for retrieving the current
Expand All @@ -306,17 +302,14 @@ public object StytchB2BClient {
* StytchB2BClient.configure()
*/
@JvmStatic
public val organization: Organization =
public val organization: Organization by StytchLazyDelegate(::assertInitialized) {
OrganizationImpl(
externalScope,
dispatchers,
sessionStorage,
StytchB2BApi.Organization,
)
get() {
assertInitialized()
return field
}
}

/**
* Exposes an instance of the [Member] interface which provides methods for retrieving the current authenticated
Expand All @@ -326,17 +319,14 @@ public object StytchB2BClient {
* StytchB2BClient.configure()
*/
@JvmStatic
public val member: Member =
public val member: Member by StytchLazyDelegate(::assertInitialized) {
MemberImpl(
externalScope,
dispatchers,
sessionStorage,
StytchB2BApi.Member,
)
get() {
assertInitialized()
return field
}
}

/**
* Exposes an instance of the [Passwords] interface which provides methods for authenticating passwords, resetting
Expand All @@ -346,18 +336,15 @@ public object StytchB2BClient {
* StytchB2BClient.configure()
*/
@JvmStatic
public val passwords: Passwords =
public val passwords: Passwords by StytchLazyDelegate(::assertInitialized) {
PasswordsImpl(
externalScope,
dispatchers,
sessionStorage,
StytchB2BApi.Passwords,
pkcePairManager,
)
get() {
assertInitialized()
return field
}
}

/**
* Exposes an instance of the [Discovery] interface which provides methods for creating and discovering
Expand All @@ -367,34 +354,28 @@ public object StytchB2BClient {
* StytchB2BClient.configure()
*/
@JvmStatic
public val discovery: Discovery =
public val discovery: Discovery by StytchLazyDelegate(::assertInitialized) {
DiscoveryImpl(
externalScope,
dispatchers,
sessionStorage,
StytchB2BApi.Discovery,
)
get() {
assertInitialized()
return field
}
}

/**
* Exposes an instance of the [SSO] interface which provides methods for authenticating SSO sessions
*/
@JvmStatic
public val sso: SSO =
public val sso: SSO by StytchLazyDelegate(::assertInitialized) {
SSOImpl(
externalScope,
dispatchers,
sessionStorage,
StytchB2BApi.SSO,
pkcePairManager,
)
get() {
assertInitialized()
return field
}
}

/**
* Exposes an instance of the [DFP] interface which provides a method for retrieving a dfp_telemetry_id for use
Expand All @@ -404,16 +385,13 @@ public object StytchB2BClient {
* StytchB2BClient.configure()
*/
@JvmStatic
public val dfp: DFP by lazy {
assertInitialized()
public val dfp: DFP by StytchLazyDelegate(::assertInitialized) {
DFPImpl(dfpProvider, dispatchers, externalScope)
}

internal val events: Events
get() {
assertInitialized()
return EventsImpl(deviceInfo, appSessionId, externalScope, dispatchers, StytchB2BApi.Events)
}
internal val events: Events by StytchLazyDelegate(::assertInitialized) {
EventsImpl(deviceInfo, appSessionId, externalScope, dispatchers, StytchB2BApi.Events)
}

/**
* Exposes an instance of the [OTP] interface which provides a method for sending and authenticating OTP codes
Expand All @@ -422,11 +400,9 @@ public object StytchB2BClient {
* StytchB2BClient.configure()
*/
@JvmStatic
public val otp: OTP = OTPImpl(externalScope, dispatchers, sessionStorage, StytchB2BApi.OTP)
get() {
assertInitialized()
return field
}
public val otp: OTP by StytchLazyDelegate(::assertInitialized) {
OTPImpl(externalScope, dispatchers, sessionStorage, StytchB2BApi.OTP)
}

/**
* Exposes an instance of the [TOTP] interface which provides a method for creating and authenticating TOTP codes
Expand All @@ -435,11 +411,9 @@ public object StytchB2BClient {
* StytchB2BClient.configure()
*/
@JvmStatic
public val totp: TOTP = TOTPImpl(externalScope, dispatchers, sessionStorage, StytchB2BApi.TOTP)
get() {
assertInitialized()
return field
}
public val totp: TOTP by StytchLazyDelegate(::assertInitialized) {
TOTPImpl(externalScope, dispatchers, sessionStorage, StytchB2BApi.TOTP)
}

/**
* Exposes an instance of the [RecoveryCodes] interface which provides methods for getting, rotating, and
Expand All @@ -449,12 +423,9 @@ public object StytchB2BClient {
* StytchB2BClient.configure()
*/
@JvmStatic
public val recoveryCodes: RecoveryCodes =
public val recoveryCodes: RecoveryCodes by StytchLazyDelegate(::assertInitialized) {
RecoveryCodesImpl(externalScope, dispatchers, sessionStorage, StytchB2BApi.RecoveryCodes)
get() {
assertInitialized()
return field
}
}

/**
* Exposes an instance of the [OAuth] interface which provides a method for starting and authenticating OAuth and
Expand All @@ -464,36 +435,29 @@ public object StytchB2BClient {
* StytchB2BClient.configure()
*/
@JvmStatic
public val oauth: OAuth =
public val oauth: OAuth by StytchLazyDelegate(::assertInitialized) {
OAuthImpl(externalScope, dispatchers, sessionStorage, StytchB2BApi.OAuth, pkcePairManager)
get() {
assertInitialized()
return field
}
}

/**
* Exposes an instance of the [RBAC] interface which provides methods for checking a member's permissions
* @throws [StytchSDKNotConfiguredError] if you attempt to access this property before calling
* StytchB2BClient.configure()
*/
@JvmStatic
public val rbac: RBAC = RBACImpl(externalScope, dispatchers, sessionStorage)
get() {
assertInitialized()
return field
}
public val rbac: RBAC by StytchLazyDelegate(::assertInitialized) {
RBACImpl(externalScope, dispatchers, sessionStorage)
}

/**
* Exposes an instance of the [SearchManager] interface which provides methods to search organizations and members
* @throws [StytchSDKNotConfiguredError] if you attempt to access this property before calling
* StytchB2BClient.configure()
*/
@JvmStatic
public val searchManager: SearchManager = SearchManagerImpl(externalScope, dispatchers, StytchB2BApi.SearchManager)
get() {
assertInitialized()
return field
}
public val searchManager: SearchManager by StytchLazyDelegate(::assertInitialized) {
SearchManagerImpl(externalScope, dispatchers, StytchB2BApi.SearchManager)
}

/**
* Exposes an instance of the [SCIM] interface which provides methods for creating, getting, updating, deleting, and
Expand All @@ -502,11 +466,9 @@ public object StytchB2BClient {
* StytchB2BClient.configure()
*/
@JvmStatic
public val scim: SCIM = SCIMImpl(externalScope, dispatchers, StytchB2BApi.SCIM)
get() {
assertInitialized()
return field
}
public val scim: SCIM by StytchLazyDelegate(::assertInitialized) {
SCIMImpl(externalScope, dispatchers, StytchB2BApi.SCIM)
}

/**
* Call this method to parse out and authenticate deeplinks that your application receives. The currently supported
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.stytch.sdk.common

import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

internal class StytchLazyDelegate<T>(
private val assertInitialized: () -> Unit,
private val initializer: () -> T,
) : ReadOnlyProperty<Any?, T> {
private var value: T? = null

override fun getValue(
thisRef: Any?,
property: KProperty<*>,
): T {
assertInitialized()
if (value == null) {
value = initializer()
}
return value!!
}
}
Loading

0 comments on commit 8652609

Please sign in to comment.