Skip to content

Commit

Permalink
Create a new TurboVisitError and TurboVisitErrorType to capture visit…
Browse files Browse the repository at this point in the history
… error information and pass to apps
  • Loading branch information
jayohms committed Feb 26, 2024
1 parent fe97e7f commit b07ccbc
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import dev.hotwire.turbo.fragments.TurboWebFragment
import dev.hotwire.turbo.nav.TurboNavGraphDestination
import dev.hotwire.turbo.views.TurboWebView
import dev.hotwire.turbo.visit.TurboVisitAction.REPLACE
import dev.hotwire.turbo.visit.TurboVisitError
import dev.hotwire.turbo.visit.TurboVisitOptions

@TurboNavGraphDestination(uri = "turbo://fragment/web")
Expand Down Expand Up @@ -58,10 +59,10 @@ open class WebFragment : TurboWebFragment(), NavDestination {
menuProgress?.isVisible = false
}

override fun onVisitErrorReceived(location: String, errorCode: Int) {
when (errorCode) {
override fun onVisitErrorReceived(location: String, error: TurboVisitError) {
when (error.code) {
401 -> navigate(SIGN_IN_URL, TurboVisitOptions(action = REPLACE))
else -> super.onVisitErrorReceived(location, errorCode)
else -> super.onVisitErrorReceived(location, error)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.view.View
import android.view.ViewGroup
import dev.hotwire.turbo.demo.R
import dev.hotwire.turbo.nav.TurboNavGraphDestination
import dev.hotwire.turbo.visit.TurboVisitError

@TurboNavGraphDestination(uri = "turbo://fragment/web/home")
class WebHomeFragment : WebFragment() {
Expand All @@ -15,7 +16,7 @@ class WebHomeFragment : WebFragment() {
}

@SuppressLint("InflateParams")
override fun createErrorView(statusCode: Int): View {
override fun createErrorView(error: TurboVisitError): View {
return layoutInflater.inflate(R.layout.error_web_home, null)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import dev.hotwire.turbo.views.TurboView
import dev.hotwire.turbo.views.TurboWebView
import dev.hotwire.turbo.visit.TurboVisit
import dev.hotwire.turbo.visit.TurboVisitAction
import dev.hotwire.turbo.visit.TurboVisitError
import dev.hotwire.turbo.visit.TurboVisitOptions
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -158,8 +159,8 @@ internal class TurboWebFragmentDelegate(
/**
* Displays the error view that's implemented via [TurboWebFragmentCallback.createErrorView].
*/
fun showErrorView(code: Int) {
turboView?.addErrorView(callback.createErrorView(code))
fun showErrorView(error: TurboVisitError) {
turboView?.addErrorView(callback.createErrorView(error))
}

// -----------------------------------------------------------------------
Expand Down Expand Up @@ -205,19 +206,19 @@ internal class TurboWebFragmentDelegate(
navDestination.fragmentViewModel.setTitle(title())
}

override fun onReceivedError(errorCode: Int) {
callback.onVisitErrorReceived(location, errorCode)
override fun onReceivedError(error: TurboVisitError) {
callback.onVisitErrorReceived(location, error)
}

override fun onRenderProcessGone() {
navigator.navigate(location, TurboVisitOptions(action = TurboVisitAction.REPLACE))
}

override fun requestFailedWithStatusCode(visitHasCachedSnapshot: Boolean, statusCode: Int) {
override fun requestFailedWithError(visitHasCachedSnapshot: Boolean, error: TurboVisitError) {
if (visitHasCachedSnapshot) {
callback.onVisitErrorReceivedWithCachedSnapshotAvailable(location, statusCode)
callback.onVisitErrorReceivedWithCachedSnapshotAvailable(location, error)
} else {
callback.onVisitErrorReceived(location, statusCode)
callback.onVisitErrorReceived(location, error)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import dev.hotwire.turbo.delegates.TurboWebFragmentDelegate
import dev.hotwire.turbo.util.TURBO_REQUEST_CODE_FILES
import dev.hotwire.turbo.views.TurboView
import dev.hotwire.turbo.views.TurboWebChromeClient
import dev.hotwire.turbo.visit.TurboVisitError

/**
* The base class from which all bottom sheet web fragments in a
Expand Down Expand Up @@ -82,15 +83,15 @@ abstract class TurboWebBottomSheetDialogFragment : TurboBottomSheetDialogFragmen
}

@SuppressLint("InflateParams")
override fun createErrorView(statusCode: Int): View {
override fun createErrorView(error: TurboVisitError): View {
return layoutInflater.inflate(R.layout.turbo_error, null)
}

override fun createWebChromeClient(): TurboWebChromeClient {
return TurboWebChromeClient(session)
}

override fun onVisitErrorReceived(location: String, errorCode: Int) {
webDelegate.showErrorView(errorCode)
override fun onVisitErrorReceived(location: String, error: TurboVisitError) {
webDelegate.showErrorView(error)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import dev.hotwire.turbo.session.TurboSessionModalResult
import dev.hotwire.turbo.util.TURBO_REQUEST_CODE_FILES
import dev.hotwire.turbo.views.TurboView
import dev.hotwire.turbo.views.TurboWebChromeClient
import dev.hotwire.turbo.visit.TurboVisitError

/**
* The base class from which all web "standard" fragments (non-dialogs) in a
Expand Down Expand Up @@ -94,15 +95,15 @@ abstract class TurboWebFragment : TurboFragment(), TurboWebFragmentCallback {
}

@SuppressLint("InflateParams")
override fun createErrorView(statusCode: Int): View {
override fun createErrorView(error: TurboVisitError): View {
return layoutInflater.inflate(R.layout.turbo_error, null)
}

override fun createWebChromeClient(): TurboWebChromeClient {
return TurboWebChromeClient(session)
}

override fun onVisitErrorReceived(location: String, errorCode: Int) {
webDelegate.showErrorView(errorCode)
override fun onVisitErrorReceived(location: String, error: TurboVisitError) {
webDelegate.showErrorView(error)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.webkit.HttpAuthHandler
import dev.hotwire.turbo.views.TurboView
import dev.hotwire.turbo.views.TurboWebChromeClient
import dev.hotwire.turbo.views.TurboWebView
import dev.hotwire.turbo.visit.TurboVisitError

/**
* Callback interface to be implemented by a [TurboWebFragment],
Expand All @@ -19,7 +20,7 @@ interface TurboWebFragmentCallback {
/**
* Inflate and return a new view to serve as an error view.
*/
fun createErrorView(statusCode: Int): View
fun createErrorView(error: TurboVisitError): View

/**
* Inflate and return a new view to serve as a progress view.
Expand Down Expand Up @@ -71,7 +72,7 @@ interface TurboWebFragmentCallback {
/**
* Called when a Turbo visit resulted in an error.
*/
fun onVisitErrorReceived(location: String, errorCode: Int) {}
fun onVisitErrorReceived(location: String, error: TurboVisitError) {}

/**
* Called when a Turbo form submission has started.
Expand All @@ -87,7 +88,7 @@ interface TurboWebFragmentCallback {
* Called when the Turbo visit resulted in an error, but a cached
* snapshot is being displayed, which may be stale.
*/
fun onVisitErrorReceivedWithCachedSnapshotAvailable(location: String, errorCode: Int) {}
fun onVisitErrorReceivedWithCachedSnapshotAvailable(location: String, error: TurboVisitError) {}

/**
* Called when the WebView has received an HTTP authentication request.
Expand Down
54 changes: 44 additions & 10 deletions turbo/src/main/kotlin/dev/hotwire/turbo/session/TurboSession.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import dev.hotwire.turbo.util.*
import dev.hotwire.turbo.views.TurboWebView
import dev.hotwire.turbo.visit.TurboVisit
import dev.hotwire.turbo.visit.TurboVisitAction
import dev.hotwire.turbo.visit.TurboVisitError
import dev.hotwire.turbo.visit.TurboVisitErrorType
import dev.hotwire.turbo.visit.TurboVisitOptions
import kotlinx.coroutines.*
import java.util.*
Expand Down Expand Up @@ -283,6 +285,12 @@ class TurboSession internal constructor(
*/
@JavascriptInterface
fun visitRequestFailedWithStatusCode(visitIdentifier: String, visitHasCachedSnapshot: Boolean, statusCode: Int) {
val visitError = TurboVisitError(
type = TurboVisitErrorType.HTTP_ERROR,
code = statusCode,
description = "Request failed"
)

logEvent(
"visitRequestFailedWithStatusCode",
"visitIdentifier" to visitIdentifier,
Expand All @@ -292,7 +300,7 @@ class TurboSession internal constructor(

currentVisit?.let { visit ->
if (visitIdentifier == visit.identifier) {
callback { it.requestFailedWithStatusCode(visitHasCachedSnapshot, statusCode) }
callback { it.requestFailedWithError(visitHasCachedSnapshot, visitError) }
}
}
}
Expand Down Expand Up @@ -477,9 +485,15 @@ class TurboSession internal constructor(
*/
@JavascriptInterface
fun turboFailedToLoad() {
logEvent("turboFailedToLoad")
val visitError = TurboVisitError(
type = TurboVisitErrorType.LOAD_ERROR,
code = -1,
description = "Turbo failed to load"
)

logEvent("turboFailedToLoad", "error" to visitError)
reset()
callback { it.onReceivedError(-1) }
callback { it.onReceivedError(visitError) }
}

/**
Expand Down Expand Up @@ -748,32 +762,52 @@ class TurboSession internal constructor(
super.onReceivedError(view, request, error)

if (request.isForMainFrame && isFeatureSupported(WEB_RESOURCE_ERROR_GET_CODE)) {
logEvent("onReceivedError", "errorCode" to error.errorCode)
val visitError = TurboVisitError(
type = TurboVisitErrorType.WEB_RESOURCE_ERROR,
code = error.errorCode,
description = if (isFeatureSupported(WEB_RESOURCE_ERROR_GET_DESCRIPTION)) {
error.description.toString()
} else {
null
}
)

logEvent("onReceivedError", "error" to visitError)
reset()
callback { it.onReceivedError(error.errorCode) }
callback { it.onReceivedError(visitError) }
}
}

override fun onReceivedHttpError(view: WebView, request: WebResourceRequest, errorResponse: WebResourceResponse) {
super.onReceivedHttpError(view, request, errorResponse)

if (request.isForMainFrame) {
logEvent("onReceivedHttpError", "statusCode" to errorResponse.statusCode)
val visitError = TurboVisitError(
type = TurboVisitErrorType.HTTP_ERROR,
code = errorResponse.statusCode,
description = errorResponse.reasonPhrase
)

logEvent("onReceivedHttpError", "error" to visitError)
reset()
callback { it.onReceivedError(errorResponse.statusCode) }
callback { it.onReceivedError(visitError) }
}
}

override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) {
super.onReceivedSslError(view, handler, error)
handler.cancel()

logEvent("onReceivedSslError", "url" to error.url)
val visitError = TurboVisitError(
type = TurboVisitErrorType.WEB_SSL_ERROR,
code = error.primaryError
)

logEvent("onReceivedSslError", "error" to visitError)
reset()
callback { it.onReceivedError(-1) }
callback { it.onReceivedError(visitError) }
}

@TargetApi(Build.VERSION_CODES.O)
override fun onRenderProcessGone(view: WebView, detail: RenderProcessGoneDetail): Boolean {
logEvent("onRenderProcessGone", "didCrash" to detail.didCrash())

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@ package dev.hotwire.turbo.session

import android.webkit.HttpAuthHandler
import dev.hotwire.turbo.nav.TurboNavDestination
import dev.hotwire.turbo.visit.TurboVisitError
import dev.hotwire.turbo.visit.TurboVisitErrorType
import dev.hotwire.turbo.visit.TurboVisitOptions

internal interface TurboSessionCallback {
fun onPageStarted(location: String)
fun onPageFinished(location: String)
fun onReceivedError(errorCode: Int)
fun onReceivedError(error: TurboVisitError)
fun onRenderProcessGone()
fun onZoomed(newScale: Float)
fun onZoomReset(newScale: Float)
fun pageInvalidated()
fun requestFailedWithStatusCode(visitHasCachedSnapshot: Boolean, statusCode: Int)
fun requestFailedWithError(visitHasCachedSnapshot: Boolean, error: TurboVisitError)
fun onReceivedHttpAuthRequest(handler: HttpAuthHandler, host: String, realm: String)
fun visitRendered()
fun visitCompleted(completedOffline: Boolean)
Expand Down
18 changes: 18 additions & 0 deletions turbo/src/main/kotlin/dev/hotwire/turbo/visit/TurboVisitError.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dev.hotwire.turbo.visit

data class TurboVisitError(
/**
*
*/
val type: TurboVisitErrorType,

/**
*
*/
val code: Int,

/**
*
*/
val description: String? = null
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dev.hotwire.turbo.visit

enum class TurboVisitErrorType {
/**
*
*/
LOAD_ERROR,

/**
*
*/
HTTP_ERROR,

/**
*
*/
WEB_RESOURCE_ERROR,

/**
*
*/
WEB_SSL_ERROR
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import dev.hotwire.turbo.nav.TurboNavDestination
import dev.hotwire.turbo.util.toJson
import dev.hotwire.turbo.views.TurboWebView
import dev.hotwire.turbo.visit.TurboVisit
import dev.hotwire.turbo.visit.TurboVisitError
import dev.hotwire.turbo.visit.TurboVisitErrorType
import dev.hotwire.turbo.visit.TurboVisitOptions
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
Expand Down Expand Up @@ -88,14 +90,32 @@ class TurboSessionTest {
assertThat(session.currentVisit?.identifier).isEqualTo(visitIdentifier)
}

@Test
fun visitFailedToLoadCallsAdapter() {
val visitIdentifier = "12345"

session.currentVisit = visit.copy(identifier = visitIdentifier)
session.turboFailedToLoad()

verify(callback).onReceivedError(TurboVisitError(
type = TurboVisitErrorType.LOAD_ERROR,
code = -1,
description = "Turbo failed to load"
))
}

@Test
fun visitRequestFailedWithStatusCodeCallsAdapter() {
val visitIdentifier = "12345"

session.currentVisit = visit.copy(identifier = visitIdentifier)
session.visitRequestFailedWithStatusCode(visitIdentifier, true, 500)

verify(callback).requestFailedWithStatusCode(true, 500)
verify(callback).requestFailedWithError(true, TurboVisitError(
type = TurboVisitErrorType.HTTP_ERROR,
code = 500,
description = "Request failed"
))
}

@Test
Expand Down

0 comments on commit b07ccbc

Please sign in to comment.