Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RNMobile] Add error boundry handling for Android #59385

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@

import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe Android Studio removed this when it linted the file.

import com.facebook.react.bridge.WritableNativeMap;

import org.wordpress.mobile.WPAndroidGlue.GutenbergJsException;
import org.wordpress.mobile.WPAndroidGlue.MediaOption;
import org.wordpress.mobile.WPAndroidGlue.RequestExecutor;

import java.util.ArrayList;
import java.util.List;

public interface GutenbergBridgeJS2Parent extends RequestExecutor {

void responseHtml(String title, String html, boolean changed, ReadableMap contentInfo);

void editorDidMount(ReadableArray unsupportedBlockNames);
Expand Down Expand Up @@ -65,6 +64,10 @@ interface ConnectionStatusCallback {
void onRequestConnectionStatus(boolean isConnected);
}

interface LogExceptionCallback {
void onLogException(boolean success);
}

// Ref: https://github.com/facebook/react-native/blob/HEAD/Libraries/polyfills/console.js#L376
enum LogLevel {
TRACE(0),
Expand Down Expand Up @@ -178,4 +181,6 @@ void gutenbergDidRequestUnsupportedBlockFallback(ReplaceUnsupportedBlockCallback
void toggleRedoButton(boolean isDisabled);

void requestConnectionStatus(ConnectionStatusCallback connectionStatusCallback);

void logException(GutenbergJsException exception, LogExceptionCallback logExceptionCallback);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@
import com.facebook.react.modules.core.DeviceEventManagerModule;

import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.ConnectionStatusCallback;
import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.LogExceptionCallback;
import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.MediaType;
import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.OtherMediaOptionsReceivedCallback;
import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.FocalPointPickerTooltipShownCallback;
import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.BlockTypeImpressionsCallback;
import org.wordpress.mobile.WPAndroidGlue.DeferredEventEmitter;
import org.wordpress.mobile.WPAndroidGlue.GutenbergJsException;
import org.wordpress.mobile.WPAndroidGlue.MediaOption;

import java.io.Serializable;
Expand Down Expand Up @@ -593,4 +595,19 @@ public void hideAndroidSoftKeyboard() {
}
}
}

@ReactMethod
public void logException(final ReadableMap rawException, final Callback jsCallback) {
GutenbergJsException exception = GutenbergJsException.fromReadableMap(rawException);
LogExceptionCallback logExceptionCallback = onLogExceptionCallback(jsCallback);
mGutenbergBridgeJS2Parent.logException(exception, logExceptionCallback);
}

private LogExceptionCallback onLogExceptionCallback(final Callback jsCallback) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm following the same encapsulation used for other callbacks in the bridge, such as ConnectionStatusCallback.

return new GutenbergBridgeJS2Parent.LogExceptionCallback() {
@Override public void onLogException(boolean success) {
jsCallback.invoke(success);
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.wordpress.mobile.WPAndroidGlue

import com.facebook.react.bridge.ReadableMap

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 fromReadableMap(rawException: ReadableMap): GutenbergJsException {
val type: String = rawException.getString("type") ?: ""
val message: String = rawException.getString("message") ?: ""

val stackTrace: List<JsExceptionStackTraceElement> = rawException.getArray("stacktrace")?.let {
(0 until it.size()).mapNotNull { index ->
val stackTraceElement = it.getMap(index)
stackTraceElement?.let {
val stackTraceFunction = stackTraceElement.getString("function")
stackTraceFunction?.let {
JsExceptionStackTraceElement(
stackTraceElement.getString("filename"),
if (stackTraceElement.hasKey("lineno")) stackTraceElement.getInt("lineno") else null,
if (stackTraceElement.hasKey("colno")) stackTraceElement.getInt("colno") else null,
stackTraceFunction
)
}
}
}
} ?: emptyList()

val context: Map<String, Any> = rawException.getMap("context")?.toHashMap() ?: emptyMap()
val tags: Map<String, String> = rawException.getMap("tags")?.toHashMap()?.mapValues { it.value.toString() } ?: emptyMap()
val isHandled: Boolean = rawException.getBoolean("isHandled")
val handledBy: String = rawException.getString("handledBy") ?: ""

return GutenbergJsException(
type,
message,
stackTrace,
context,
tags,
isHandled,
handledBy
)
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
import androidx.core.util.Pair;
import androidx.fragment.app.Fragment;

import com.BV.LinearGradient.LinearGradientPackage;
import com.brentvatne.react.ReactVideoPackage;
import com.dylanvann.fastimage.FastImageViewPackage;
import com.facebook.hermes.reactexecutor.HermesExecutorFactory;
import com.facebook.imagepipeline.backends.okhttp3.OkHttpImagePipelineConfigFactory;
import com.facebook.imagepipeline.core.ImagePipelineConfig;
Expand All @@ -40,23 +42,22 @@
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;
import com.horcrux.svg.SvgPackage;
import com.BV.LinearGradient.LinearGradientPackage;
import com.reactnativecommunity.clipboard.ClipboardPackage;
import com.reactnativecommunity.slider.ReactSliderPackage;
import org.linusu.RNGetRandomValuesPackage;
import com.reactnativecommunity.webview.RNCWebViewPackage;
import com.swmansion.gesturehandler.RNGestureHandlerPackage;
import com.swmansion.reanimated.ReanimatedPackage;
import com.swmansion.rnscreens.RNScreensPackage;
import com.th3rdwave.safeareacontext.SafeAreaContextPackage;
import org.reactnative.maskedview.RNCMaskedViewPackage;
import com.dylanvann.fastimage.FastImageViewPackage;

import org.linusu.RNGetRandomValuesPackage;
import org.reactnative.maskedview.RNCMaskedViewPackage;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.AppLog.T;
import org.wordpress.mobile.ReactNativeAztec.ReactAztecPackage;
import org.wordpress.mobile.ReactNativeGutenbergBridge.BuildConfig;
import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent;
import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.LogExceptionCallback;
import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.MediaSelectedCallback;
import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.ReplaceUnsupportedBlockCallback;
import org.wordpress.mobile.ReactNativeGutenbergBridge.RNMedia;
Expand Down Expand Up @@ -112,6 +113,8 @@ public class WPAndroidGlueCode {
private OnToggleRedoButtonListener mOnToggleRedoButtonListener;
private OnConnectionStatusEventListener mOnConnectionStatusEventListener;
private OnBackHandlerEventListener mOnBackHandlerEventListener;

private OnLogExceptionListener mOnLogExceptionListener;
private boolean mIsEditorMounted;

private String mContentHtml = "";
Expand Down Expand Up @@ -260,6 +263,10 @@ public interface OnBackHandlerEventListener {
void onBackHandler();
}

public interface OnLogExceptionListener {
void onLogException(GutenbergJsException exception, LogExceptionCallback logExceptionCallback);
}

public void mediaSelectionCancelled() {
mAppendsMultipleSelectedToSiblingBlocks = false;
}
Expand Down Expand Up @@ -561,6 +568,11 @@ public void requestConnectionStatus(ConnectionStatusCallback connectionStatusCal
boolean isConnected = mOnConnectionStatusEventListener.onRequestConnectionStatus();
connectionStatusCallback.onRequestConnectionStatus(isConnected);
}

@Override
public void logException(GutenbergJsException exception, LogExceptionCallback logExceptionCallback) {
mOnLogExceptionListener.onLogException(exception, logExceptionCallback);
}
}, mIsDarkMode);

return Arrays.asList(
Expand Down Expand Up @@ -659,6 +671,7 @@ public void attachToContainer(ViewGroup viewGroup,
OnToggleRedoButtonListener onToggleRedoButtonListener,
OnConnectionStatusEventListener onConnectionStatusEventListener,
OnBackHandlerEventListener onBackHandlerEventListener,
OnLogExceptionListener onLogExceptionListener,
boolean isDarkMode) {
MutableContextWrapper contextWrapper = (MutableContextWrapper) mReactRootView.getContext();
contextWrapper.setBaseContext(viewGroup.getContext());
Expand All @@ -684,6 +697,7 @@ public void attachToContainer(ViewGroup viewGroup,
mOnToggleRedoButtonListener = onToggleRedoButtonListener;
mOnConnectionStatusEventListener = onConnectionStatusEventListener;
mOnBackHandlerEventListener = onBackHandlerEventListener;
mOnLogExceptionListener = onLogExceptionListener;

sAddCookiesInterceptor.setOnAuthHeaderRequestedListener(onAuthHeaderRequestedListener);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergBridgeJS2Parent.
import org.wordpress.mobile.ReactNativeGutenbergBridge.GutenbergWebViewActivity
import org.wordpress.mobile.ReactNativeGutenbergBridge.RNMedia
import org.wordpress.mobile.ReactNativeGutenbergBridge.RNReactNativeGutenbergBridgePackage
import org.wordpress.mobile.WPAndroidGlue.GutenbergJsException
import org.wordpress.mobile.WPAndroidGlue.Media
import org.wordpress.mobile.WPAndroidGlue.MediaOption

Expand Down Expand Up @@ -218,6 +219,8 @@ class MainApplication : Application(), ReactApplication, GutenbergBridgeInterfac
override fun requestConnectionStatus(connectionStatusCallback: ConnectionStatusCallback) {
connectionStatusCallback.onRequestConnectionStatus(true)
}

override fun logException(exception: GutenbergJsException, logExceptionCallback: GutenbergBridgeJS2Parent.LogExceptionCallback) {}
}, isDarkMode)
}

Expand Down
Loading