Skip to content

Commit 5d91bbd

Browse files
authored
Various fixes (#233)
1 parent 4773dec commit 5d91bbd

6 files changed

Lines changed: 80 additions & 23 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ written for Android. You can read more on the [Descope Website](https://descope.
99
Add the following to your `build.gradle` dependencies:
1010

1111
```groovy
12-
implementation 'com.descope:descope-kotlin:0.16.0'
12+
implementation 'com.descope:descope-kotlin:0.16.1'
1313
```
1414

1515
## Quickstart

descopesdk/src/main/java/com/descope/android/DescopeFlowCoordinator.kt

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,10 @@ import com.descope.session.DescopeSession
4949
import com.descope.session.Token
5050
import com.descope.types.AuthenticationResponse
5151
import com.descope.types.DescopeException
52+
import kotlinx.coroutines.CoroutineScope
5253
import kotlinx.coroutines.Dispatchers
53-
import kotlinx.coroutines.GlobalScope
54+
import kotlinx.coroutines.Job
55+
import kotlinx.coroutines.Runnable
5456
import kotlinx.coroutines.launch
5557
import org.json.JSONObject
5658
import java.lang.ref.WeakReference
@@ -59,6 +61,9 @@ import java.util.Timer
5961
import java.util.TimerTask
6062
import kotlin.concurrent.timer
6163

64+
private const val retryWindow = 10 * 1000L
65+
private const val retryInterval = 1250L
66+
6267
@SuppressLint("SetJavaScriptEnabled")
6368
class DescopeFlowCoordinator(val webView: WebView) {
6469

@@ -75,6 +80,9 @@ class DescopeFlowCoordinator(val webView: WebView) {
7580
get() = if (flow?.sessionProvider != null) flow?.sessionProvider?.invoke() else sdk?.sessionManager?.session?.takeIf { !it.refreshToken.isExpired }
7681
private var currentFlowUrl: Uri? = null
7782
private var alreadySetUp = false
83+
private var startedAt: Long = 0L
84+
private var attempts: Int = 0
85+
private var loadFailure: Boolean = false
7886

7987
init {
8088
webView.settings.javaScriptEnabled = true
@@ -127,11 +135,7 @@ class DescopeFlowCoordinator(val webView: WebView) {
127135
return
128136
}
129137
currentFlowUrl = url.toUri()
130-
val scope = webView.findViewTreeLifecycleOwner()?.lifecycleScope
131-
if (scope == null) {
132-
logger.error("Unable to find lifecycle owner coroutine scope")
133-
return
134-
}
138+
val scope = webView.findViewTreeLifecycleOwner()?.lifecycleScope ?: CoroutineScope(Job())
135139
scope.launch(Dispatchers.Main) {
136140
var type: String
137141
var canceled = false
@@ -179,7 +183,7 @@ class DescopeFlowCoordinator(val webView: WebView) {
179183
type = "failure"
180184
val failure = when (e) {
181185
DescopeException.oauthNativeCancelled -> {
182-
logger.info("OAuth native canceled" )
186+
logger.info("OAuth native canceled")
183187
canceled = true
184188
"OAuthNativeCancelled"
185189
}
@@ -243,6 +247,11 @@ class DescopeFlowCoordinator(val webView: WebView) {
243247
}
244248

245249
override fun onPageFinished(view: WebView?, url: String?) {
250+
if (loadFailure) {
251+
logger.info("Page finished after error", url)
252+
loadFailure = false
253+
return
254+
}
246255
logger.info("On page finished", url, view?.progress)
247256
if (alreadySetUp) {
248257
logger.error("Bridge is already set up", url, view?.progress)
@@ -271,6 +280,7 @@ class DescopeFlowCoordinator(val webView: WebView) {
271280
override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {
272281
if (request?.isForMainFrame == true) {
273282
logger.error("Error loading flow page", error?.errorCode, error?.description)
283+
if (scheduleRetryAfterError()) return
274284
val code = error?.errorCode ?: 0
275285
val failure = error?.description?.toString() ?: ""
276286
val message = when (code) {
@@ -288,11 +298,28 @@ class DescopeFlowCoordinator(val webView: WebView) {
288298
if (request?.isForMainFrame == true) {
289299
logger.error("Flow page failed to load", errorResponse?.statusCode)
290300
val statusCode = errorResponse?.statusCode ?: 0
301+
if (statusCode >= 500 && scheduleRetryAfterError()) return // potentially retry only on server errors
291302
val message = failureFromResponseCode(statusCode)
292303
val exception = DescopeException.networkError.with(message = message)
293304
handleError(exception)
294305
}
295306
}
307+
308+
private fun scheduleRetryAfterError(): Boolean {
309+
val retryIn = attempts * retryInterval
310+
if (
311+
alreadySetUp // initial loading was successful
312+
|| System.currentTimeMillis() - startedAt + retryIn > retryWindow // or retry window exceeded
313+
) {
314+
return false
315+
}
316+
317+
loadFailure = true
318+
logger.info("Will retry to load in $retryIn ms")
319+
val ref = WeakReference(this@DescopeFlowCoordinator)
320+
handler.postDelayed(createRetryRunnable(ref), retryIn)
321+
return true
322+
}
296323
}
297324
}
298325

@@ -324,11 +351,18 @@ document.head.appendChild(element)
324351

325352
// Internal API
326353

327-
internal fun run(flow: DescopeFlow) {
354+
internal fun startFlow(flow: DescopeFlow) {
328355
this.flow = flow
329356
handleStarted()
330357
webView.loadUrl(flow.url)
331358
}
359+
360+
internal fun reloadFlow() {
361+
val flowUrl = flow?.url ?: return
362+
attempts++
363+
logger.info("Retrying to load flow (attempt $attempts)")
364+
webView.loadUrl(flowUrl)
365+
}
332366

333367
internal fun resumeFromDeepLink(deepLink: Uri) {
334368
if (flow == null) {
@@ -364,6 +398,8 @@ document.head.appendChild(element)
364398
if (state == Started) return
365399
state = Started
366400
alreadySetUp = false
401+
startedAt = System.currentTimeMillis()
402+
attempts++
367403
executeHooks(Event.Started)
368404
}
369405

@@ -378,6 +414,7 @@ document.head.appendChild(element)
378414
logger.info("Flow is ready ($tag)")
379415
startTimer()
380416
state = Ready
417+
attempts = 0
381418
executeHooks(Event.Ready)
382419
listener?.onReady()
383420
}
@@ -391,20 +428,22 @@ document.head.appendChild(element)
391428
if (state == Failed) return
392429

393430
handler.post {
394-
logger.error("Flow failed with ${e.code}) error", e)
431+
logger.error("Flow failed with [${e.code}] error", e)
395432
stopTimer()
396433
state = Failed
434+
attempts = 0
397435
listener?.onError(e)
398436
}
399437
}
400438

401439
private fun handleSuccess(authResponse: AuthenticationResponse) {
402-
if (ensureState(Ready)) return
440+
if (ensureState(Started, Ready)) return
403441
handler.post {
404442
val res = if (logger.isUnsafeEnabled) authResponse else null
405443
logger.info("Flow finished successfully", res)
406444
stopTimer()
407445
state = Finished
446+
attempts = 0
408447
listener?.onSuccess(authResponse)
409448
}
410449
}
@@ -661,3 +700,16 @@ private fun createTimerAction(ref: WeakReference<DescopeFlowCoordinator>): (Time
661700
}
662701
}
663702
}
703+
704+
private fun createRetryRunnable(ref: WeakReference<DescopeFlowCoordinator>): (Runnable) {
705+
return object : Runnable {
706+
override fun run() {
707+
val coordinator = ref.get()
708+
if (coordinator == null) {
709+
return
710+
} else {
711+
coordinator.reloadFlow()
712+
}
713+
}
714+
}
715+
}

descopesdk/src/main/java/com/descope/android/DescopeFlowView.kt

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,10 @@ import android.util.AttributeSet
66
import android.view.ViewGroup
77
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
88
import android.webkit.WebView
9-
import androidx.browser.customtabs.CustomTabsIntent
109
import com.descope.Descope
11-
import com.descope.sdk.DescopeSdk
1210
import com.descope.session.DescopeSession
1311
import com.descope.types.AuthenticationResponse
1412
import com.descope.types.DescopeException
15-
import com.descope.types.OAuthProvider
1613

1714
/**
1815
* Authenticate a user using Descope Flows.
@@ -135,15 +132,15 @@ class DescopeFlowView : ViewGroup {
135132
// API
136133

137134
/**
138-
* Run a flow based on the configuration provided
135+
* Start a flow based on the configuration provided
139136
* via a [DescopeFlowView]
140137
*
141138
* @param flow The [DescopeFlow] to execute
142139
*/
143-
fun run(flow: DescopeFlow) {
144-
flowCoordinator.run(flow)
140+
fun startFlow(flow: DescopeFlow) {
141+
flowCoordinator.startFlow(flow)
145142
}
146-
143+
147144
/**
148145
* Resume an already running flow after a deep link
149146
* event. This function should be called to complete `Magic Link`
@@ -231,4 +228,12 @@ class DescopeFlowView : ViewGroup {
231228
Inline,
232229
DoNothing,
233230
}
231+
232+
// Deprecated
233+
234+
@Deprecated("Use startFlow instead", replaceWith = ReplaceWith("startFlow(flow)"))
235+
fun run(flow: DescopeFlow) {
236+
startFlow(flow)
237+
}
238+
234239
}

descopesdk/src/main/java/com/descope/internal/http/ClientErrors.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ internal fun failureFromResponseCode(code: Int): String? {
3030
403 -> "The request was forbidden"
3131
404 -> "The resource was not found"
3232
500, 503 -> "The request failed with status code $code"
33-
in 500..599 -> "The server was unreachable"
33+
in 500..599 -> "The server was unreachable with status code $code"
3434
else -> "The server returned status code $code"
3535
}
3636
}

descopesdk/src/main/java/com/descope/sdk/Config.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,21 +83,21 @@ open class DescopeLogger(open val level: Level, open val unsafe: Boolean) {
8383
/**
8484
* A simple logger that prints basic error and info logs using `println`.
8585
*/
86-
val basicLogger: DescopeLogger = ConsoleLogger.basic
86+
val basicLogger: DescopeLogger by lazy { ConsoleLogger.basic }
8787

8888
/**
8989
* A simple logger that prints all logs using `println`, but does not output any
9090
* potentially unsafe runtime values unless a debugger is attached.
9191
*/
92-
val debugLogger: DescopeLogger = ConsoleLogger.debug
92+
val debugLogger: DescopeLogger by lazy { ConsoleLogger.debug }
9393

9494
/**
9595
* A simple logger that prints all logs using `println`, including potentially unsafe
9696
* runtime values such as secrets, personal information, network payloads, etc.
9797
*
9898
* - **IMPORTANT**: Do not use unsafeLogger in release builds intended for production.
9999
*/
100-
val unsafeLogger: DescopeLogger = ConsoleLogger.unsafe
100+
val unsafeLogger: DescopeLogger by lazy { ConsoleLogger.unsafe }
101101
}
102102

103103
/** The severity of a log message. */

descopesdk/src/main/java/com/descope/sdk/Sdk.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,6 @@ class DescopeSdk(context: Context, projectId: String, configure: DescopeConfig.(
7878
const val NAME = "DescopeAndroid"
7979

8080
/** The Descope SDK version */
81-
const val VERSION = "0.16.0"
81+
const val VERSION = "0.16.1"
8282
}
8383
}

0 commit comments

Comments
 (0)