Skip to content

Commit

Permalink
feat: Catch and log exceptions (#64)
Browse files Browse the repository at this point in the history
* feat: Add rudimentary error boundary

Catch render errors with Gutenberg's default error boundary component.

* feat: Log render errors to the host app

Allow the host app to act on errors.

* feat: Log error details to iOS host app

Allow the iOS host app to act on errors.

* feat: Parse errors

Extract relevant attributes for logging to monitoring services.

* refactor: Hoist error boundary into layout component

Catch errors within the editor component.

* build: Add Gson

Enable parsing stringified JSON objects sent from WebViews to the host
Android app.

* feat: Add logic for logging exceptions to Android host

Enable the Android host app to act upon editor exceptions.

* task: Capture build output

* refactor: Improve editor exception type safety

* refactor: Rename "error" to "exception"

The latter better describes these "handled" errors.
  • Loading branch information
dcalhoun authored Jan 28, 2025
1 parent f8c5c41 commit fb31301
Show file tree
Hide file tree
Showing 27 changed files with 1,328 additions and 830 deletions.
1 change: 1 addition & 0 deletions android/Gutenberg/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ dependencies {
implementation(libs.androidx.appcompat)
implementation(libs.material)
implementation(libs.androidx.webkit)
implementation(libs.gson)

testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.wordpress.gutenberg

import com.google.gson.Gson
import com.google.gson.JsonObject

data class JsExceptionStackTraceElement (
val fileName: String?,
val lineNumber: Int?,
val colNumber: Int?,
val function: String,
)
class GutenbergJsException (
val type: String,
val message: String,
var stackTrace: List<JsExceptionStackTraceElement>,
val context: Map<String, Any> = emptyMap(),
val tags: Map<String,String> = emptyMap(),
val isHandled: Boolean,
val handledBy: String
) {

companion object {
@JvmStatic
fun fromString(exceptionString: String): GutenbergJsException {
val gson = Gson()
val rawException = gson.fromJson(exceptionString, JsonObject::class.java)

val type = rawException.get("type")?.asString ?: ""
val message = rawException.get("message")?.asString ?: ""

val stackTrace = rawException.getAsJsonArray("stacktrace")?.map { element ->
val stackTraceElement = element.asJsonObject
val stackTraceFunction = stackTraceElement.get("function")?.asString
stackTraceFunction?.let {
JsExceptionStackTraceElement(
stackTraceElement.get("filename")?.asString,
stackTraceElement.get("lineno")?.asInt,
stackTraceElement.get("colno")?.asInt,
stackTraceFunction
)
}
}?.filterNotNull() ?: emptyList()

val context = rawException.getAsJsonObject("context")?.entrySet()?.associate {
it.key to it.value.asString
} ?: emptyMap()

val tags = rawException.getAsJsonObject("tags")?.entrySet()?.associate {
it.key to it.value.asString
} ?: emptyMap()

val isHandled = rawException.get("isHandled")?.asBoolean ?: false
val handledBy = rawException.get("handledBy")?.asString ?: ""

return GutenbergJsException(
type,
message,
stackTrace,
context,
tags,
isHandled,
handledBy
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class GutenbergView : WebView {
private var historyChangeListener: HistoryChangeListener? = null
private var openMediaLibraryListener: OpenMediaLibraryListener? = null
private var editorDidBecomeAvailableListener: EditorAvailableListener? = null
private var logJsExceptionListener: LogJsExceptionListener? = null

var textEditorEnabled: Boolean = false
set(value) {
Expand All @@ -78,6 +79,10 @@ class GutenbergView : WebView {
openMediaLibraryListener = listener
}

fun setLogJsExceptionListener(listener: LogJsExceptionListener) {
logJsExceptionListener = listener
}

fun setOnFileChooserRequestedListener(listener: (Intent, Int) -> Unit) {
onFileChooserRequested = listener
}
Expand Down Expand Up @@ -305,6 +310,10 @@ class GutenbergView : WebView {
fun onEditorAvailable(view: GutenbergView?)
}

interface LogJsExceptionListener {
fun onLogJsException(exception: GutenbergJsException)
}

fun getTitleAndContent(callback: TitleAndContentCallback, clearFocus: Boolean = true) {
if (!isEditorLoaded) {
Log.e("GutenbergView", "You can't change the editor content until it has loaded")
Expand Down Expand Up @@ -422,6 +431,12 @@ class GutenbergView : WebView {
this.evaluateJavascript("editor.setMediaUploadAttachment($media);", null)
}

@JavascriptInterface
fun onEditorExceptionLogged(exception: String) {
val parsedException = GutenbergJsException.fromString(exception)
logJsExceptionListener?.onLogJsException(parsedException)
}

@JavascriptInterface
fun showBlockPicker() {
Log.i("GutenbergView", "BlockPickerShouldShow")
Expand Down
2 changes: 2 additions & 0 deletions android/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ material = "1.12.0"
activity = "1.9.0"
constraintlayout = "2.1.4"
webkit = "1.11.0"
gson = "2.8.9"

[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
Expand All @@ -21,6 +22,7 @@ material = { group = "com.google.android.material", name = "material", version.r
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
androidx-webkit = { group = "androidx.webkit", name = "webkit", version.ref = "webkit" }
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
Expand Down
830 changes: 830 additions & 0 deletions ios/Sources/GutenbergKit/Gutenberg/assets/index-CVihFZVi.js

Large diffs are not rendered by default.

This file was deleted.

This file was deleted.

Large diffs are not rendered by default.

Loading

0 comments on commit fb31301

Please sign in to comment.