diff --git a/cpp/TransformerTextInputRuntime.cpp b/cpp/TransformerTextInputRuntime.cpp index 07a67e7..80be8e4 100644 --- a/cpp/TransformerTextInputRuntime.cpp +++ b/cpp/TransformerTextInputRuntime.cpp @@ -4,6 +4,19 @@ namespace rntti { namespace { std::weak_ptr gUiRuntime; + +void LogTransformerError(jsi::Runtime &runtime, const std::string &message) { + try { + auto console = runtime.global().getPropertyAsObject(runtime, "console"); + auto errorFunction = console.getPropertyAsFunction(runtime, "error"); + errorFunction.call( + runtime, + jsi::String::createFromUtf8( + runtime, "[rntti] Transformer threw an error: " + message)); + } catch (const jsi::JSIException &) { + // console is not guaranteed to exist on the worklet runtime. + } +} } // namespace void SetUIWorkletRuntime( @@ -59,31 +72,47 @@ std::optional RunTransformer( auto transformerFunction = transformerValue.asObject(jsiRuntime).asFunction(jsiRuntime); - auto resultValue = uiRuntime->runSync( - transformerFunction, - jsi::String::createFromUtf8(jsiRuntime, value), - jsi::Value(selection.start), - jsi::Value(selection.end), - jsi::Value(transform)); - - TransformResult result; - auto resultObject = resultValue.asObject(jsiRuntime); - auto valueProp = resultObject.getProperty(jsiRuntime, "value"); - if (valueProp.isString()) { - result.value = valueProp.asString(jsiRuntime).utf8(jsiRuntime); - } - auto selectionProp = resultObject.getProperty(jsiRuntime, "selection"); - if (selectionProp.isObject()) { - auto selectionObject = selectionProp.asObject(jsiRuntime); - auto startProp = selectionObject.getProperty(jsiRuntime, "start"); - auto endProp = selectionObject.getProperty(jsiRuntime, "end"); - result.selection = SelectionRange{ - static_cast(startProp.asNumber()), - static_cast(endProp.asNumber())}; - } + try { + auto resultValue = uiRuntime->runSync( + transformerFunction, + jsi::String::createFromUtf8(jsiRuntime, value), + jsi::Value(selection.start), + jsi::Value(selection.end), + jsi::Value(transform)); + + // In debug builds runSync guards the call: a throwing transformer is + // reported to LogBox and undefined is returned instead of a result + // object. + if (!resultValue.isObject()) { + return std::nullopt; + } - return result; + TransformResult result; + auto resultObject = resultValue.asObject(jsiRuntime); + auto valueProp = resultObject.getProperty(jsiRuntime, "value"); + if (valueProp.isString()) { + result.value = valueProp.asString(jsiRuntime).utf8(jsiRuntime); + } + + auto selectionProp = resultObject.getProperty(jsiRuntime, "selection"); + if (selectionProp.isObject()) { + auto selectionObject = selectionProp.asObject(jsiRuntime); + auto startProp = selectionObject.getProperty(jsiRuntime, "start"); + auto endProp = selectionObject.getProperty(jsiRuntime, "end"); + result.selection = SelectionRange{ + static_cast(startProp.asNumber()), + static_cast(endProp.asNumber())}; + } + + return result; + } catch (const jsi::JSError &error) { + LogTransformerError(jsiRuntime, error.getMessage()); + return std::nullopt; + } catch (const jsi::JSIException &error) { + LogTransformerError(jsiRuntime, error.what()); + return std::nullopt; + } } } // namespace rntti diff --git a/example/src/App.tsx b/example/src/App.tsx index 6d79418..ccaf7ca 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -34,6 +34,18 @@ const usernameTransformer = new Transformer(({ value }) => { return { value: result }; }); +// Returns an out-of-bounds selection, which makes the library's selection +// validation throw on every change. Typing in this input must log the error +// instead of crashing the app. +const throwingTransformer = new Transformer(({ value }) => { + 'worklet'; + + return { + value, + selection: { start: value.length + 5, end: value.length + 5 }, + }; +}); + const phoneNumberTransformer = new PhoneNumberTransformer(); const creditCardTransformer = new PatternTransformer({ @@ -241,6 +253,15 @@ function AppContent() { /> + + ); }