Skip to content

Commit 82407a0

Browse files
committed
feat(flow): parse bridge errors into typed DescopeException
Move parseServerError from internal/http/ClientErrors.kt to internal/others/Errors.kt — the parser is a general-purpose JSON-to- DescopeException converter, not an HTTP concern. DescopeClient picks it up at its new location; ClientErrors.kt is left with the HTTP response-code handling that's actually its job. Apply parseServerError to the JS-bridge error channel too: FlowBridge.bridgeOnError now parses the incoming error string as a typed DescopeException. Server-emitted flow cancellation (errorCode E102122) collapses onto the existing DescopeException.flowCancelled identity, carrying the server's errorMessage as the exception message for context. Relates to descope/etc#16441
1 parent 6fb431e commit 82407a0

6 files changed

Lines changed: 46 additions & 18 deletions

File tree

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import com.descope.internal.others.debug
1919
import com.descope.internal.others.error
2020
import com.descope.internal.others.info
2121
import com.descope.internal.others.isUnsafeEnabled
22+
import com.descope.internal.others.parseServerError
2223
import com.descope.internal.others.stringOrEmptyAsNull
2324
import com.descope.internal.others.with
2425
import com.descope.internal.routes.isWebAuthnSupported
@@ -103,8 +104,15 @@ internal class FlowBridge(val webView: WebView) {
103104
}
104105

105106
private fun bridgeOnError(error: String) {
107+
val parsed = parseServerError(error)
108+
val exception = when {
109+
parsed == null -> DescopeException.flowFailed.with(message = error)
110+
// Convert server flow cancellation to local flow cancellation for cohesive error handling
111+
parsed.code == "E102122" -> DescopeException.flowCancelled.with(message = parsed.message)
112+
else -> parsed
113+
}
106114
handler.post {
107-
listener?.onError(DescopeException.flowFailed.with(message = error))
115+
listener?.onError(exception)
108116
}
109117
}
110118

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

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,7 @@
11
package com.descope.internal.http
22

3-
import com.descope.internal.others.toMap
43
import com.descope.internal.others.with
54
import com.descope.types.DescopeException
6-
import org.json.JSONObject
7-
8-
internal fun parseServerError(response: String): DescopeException? {
9-
try {
10-
val map = JSONObject(response).toMap()
11-
val code = map["errorCode"] as? String ?: return null
12-
val desc = map["errorDescription"] as? String ?: "Descope server error"
13-
val message = map["errorMessage"] as? String
14-
return DescopeException(code = code, desc = desc, message = message)
15-
} catch (_: Exception) {
16-
return null
17-
}
18-
}
195

206
internal fun exceptionFromResponseCode(code: Int): DescopeException? {
217
val desc = failureFromResponseCode(code) ?: return null

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.descope.internal.http
22

33
import com.descope.android.SystemInfo
4+
import com.descope.internal.others.parseServerError
45
import com.descope.sdk.DescopeConfig
56
import com.descope.sdk.DescopeSdk
67
import com.descope.types.DeliveryMethod

descopesdk/src/main/java/com/descope/internal/others/ActivityHelper.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ internal val activityHelper = object : ActivityHelper {
2323

2424
override fun openCustomTab(context: Context, customTabsIntent: CustomTabsIntent, url: Uri, onCancel: (() -> Unit)?) {
2525
this.customTabsIntent = customTabsIntent
26-
this.cancelCallback = onCancel
2726
if (!isBrowserSupported(context, url)) {
2827
throw DescopeException.customTabFailed.with(message = "No browser application was found")
2928
}
@@ -35,6 +34,7 @@ internal val activityHelper = object : ActivityHelper {
3534
} catch (e: Exception) {
3635
throw DescopeException.customTabFailed.with(message = "Failed to open custom tab from context", cause = e)
3736
}
37+
this.cancelCallback = onCancel
3838
}
3939

4040
override fun closeCustomTab(context: Context) {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
11
package com.descope.internal.others
22

33
import com.descope.types.DescopeException
4+
import org.json.JSONObject
45

56
internal fun DescopeException.with(desc: String? = null, message: String? = null, cause: Throwable? = null) = DescopeException(
67
code = code,
78
desc = desc ?: this.desc,
89
message = message ?: this.message,
910
cause = cause ?: this.cause,
1011
)
12+
13+
internal fun parseServerError(response: String): DescopeException? {
14+
try {
15+
val map = JSONObject(response).toMap()
16+
val code = map["errorCode"] as? String ?: return null
17+
val desc = map["errorDescription"] as? String ?: "Descope server error"
18+
val message = map["errorMessage"] as? String
19+
return DescopeException(code = code, desc = desc, message = message)
20+
} catch (_: Exception) {
21+
return null
22+
}
23+
}

descopesdk/src/test/java/com/descope/types/DescopeExceptionTest.kt

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package com.descope.types
22

3-
import com.descope.internal.http.parseServerError
3+
import com.descope.internal.others.parseServerError
44
import org.json.JSONObject
55
import org.junit.Assert.assertEquals
6+
import org.junit.Assert.assertNull
67
import org.junit.Assert.fail
78
import org.junit.Test
89

@@ -22,5 +23,24 @@ class DescopeExceptionTest {
2223
else -> fail("wrong when clause")
2324
}
2425
}
25-
26+
27+
@Test
28+
fun error_message_preserved() {
29+
val payload = JSONObject().apply {
30+
put("errorCode", "E102122")
31+
put("errorDescription", "Flow aborted")
32+
put("errorMessage", "User canceled")
33+
}.toString()
34+
val exception = parseServerError(payload)
35+
assertEquals("E102122", exception?.code)
36+
assertEquals("Flow aborted", exception?.desc)
37+
assertEquals("User canceled", exception?.message)
38+
}
39+
40+
@Test
41+
fun invalid_error_payload_parsing() {
42+
assertNull(parseServerError("not a json error"))
43+
assertNull(parseServerError(JSONObject().apply { put("errorMessage", "no code here") }.toString()))
44+
}
45+
2646
}

0 commit comments

Comments
 (0)