React Native Stack Traces that point to original files - react-native

When I have an error in my React Native app, the stack trace that is printed to the console points to index.bundle instead of the original source code (see example below). Is there a way to configure React Native to use source maps so that the logs show up correctly?
This problem only occurs when throwing an error from an asynchronous callback or something outside of rendering. If I throw an error inside a component, then the error shows the correct stack trace in the console. Interestingly, the error shows the correct stack trace all the time in LogBox.
I am running this with react-native run-android and viewing the logs through Metro. To clarify, I am trying to get this working for local debug builds, not production/release builds. Ideally the logs would show the correct stack in the console so that I do not have to symbolicate them manually or find the error in LogBox.
Example result from console.error:
Error: Connection closed
at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.myapp.local&modulesOnly=false&runModule=true:261835:40)
at forEach (native)
at flushVolatile (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.myapp.local&modulesOnly=false&runModule=true:261833:33)
at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.myapp.local&modulesOnly=false&runModule=true:262065:20)
at apply (native)
Thank you in advance!

Answering my own question. I dug into the react-native code and discovered that LogBox symbolicates stack traces by making calls to the metro development server. Instead of replicating that logic, I made the hacky solution below that ties into LogBox. I'm sure there are better ways to do this, but it works.
import { observe as observeLogBoxLogs, symbolicateLogNow } from 'react-native/Libraries/LogBox/Data/LogBoxData';
// LogBox keeps all logs that you have not viewed yet.
// When a new log comes in, we only want to print out the new ones.
let lastCount = 0;
observeLogBoxLogs(data => {
const logs = Array.from(data.logs);
const symbolicatedLogs = logs.filter(log => log.symbolicated.stack?.length);
for (let i = lastCount; i < symbolicatedLogs.length; i++) {
// use log instead of warn/error to prevent resending error to LogBox
console.log(formatLog(symbolicatedLogs[i]));
}
lastCount = symbolicatedLogs.length;
// Trigger symbolication on remaining logs because
// logs do not symbolicate until you click on LogBox
logs.filter(log => log.symbolicated.status === 'NONE').forEach(log => symbolicateLogNow(log));
});
function formatLog(log) {
const stackLines = (log.symbolicated.stack || [])
.filter(line => !line.collapse)
.map(line => ` at ${line.methodName} (${line.file}:${line.lineNumber}:${line.column})`)
.join('\n');
return `Error has been symbolicated\nError: ${log.message.content}\n${stackLines}`;
}
The error appears twice in console, first as the original, second as the symbolicated version. Here's an example of the log output now:
WARN Error: Connection closed
Error: Connection closed
at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:334491:50)
at forEach (native)
at flushVolatile (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:334489:43)
at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:334721:30)
at call (native)
at emitNone (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:340110:33)
at emit (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:340191:23)
at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:339907:24)
at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:339889:30)
at apply (native)
at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:347770:25)
at drainQueue (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:347735:45)
at apply (native)
at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:31681:26)
at _callTimer (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:31605:17)
at callTimers (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:31801:19)
at apply (native)
at __callFunction (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:25085:36)
at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:24813:31)
at __guard (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:25039:15)
at callFunctionReturnFlushedQueue (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false&app=com.logtest.local&modulesOnly=false&runModule=true:24812:21)
LOG Error has been symbolicated
Error: Connection closed
at Object.keys.forEach$argument_0 (/Users/georgeflug/projects/logtest/node_modules/mqtt/dist/mqtt.js:208:28)
at flushVolatile (/Users/georgeflug/projects/logtest/node_modules/mqtt/dist/mqtt.js:206:4)
at stream.on$argument_1 (/Users/georgeflug/projects/logtest/node_modules/mqtt/dist/mqtt.js:477:17)
at emitNone (/Users/georgeflug/projects/logtest/node_modules/mqtt/dist/mqtt.js:6471:4)
at emit (/Users/georgeflug/projects/logtest/node_modules/mqtt/dist/mqtt.js:6556:14)
at Duplexify.prototype._destroy (/Users/georgeflug/projects/logtest/node_modules/mqtt/dist/mqtt.js:6239:2)
at process.nextTick$argument_0 (/Users/georgeflug/projects/logteste/node_modules/mqtt/dist/mqtt.js:6221:4)
at Item.prototype.run (/Users/georgeflug/projects/logtest/node_modules/mqtt/dist/mqtt.js:13143:4)
at drainQueue (/Users/georgeflug/projects/logtest/node_modules/mqtt/dist/mqtt.js:13113:16)

If you click on the stacktrace (for example in terminal) will it pull up your vscode with all the associated files and to this location? At least that you might be able to back where the issue is in code?

Related

Display Wifi list in React Native

I need to display available WiFi network's list.
By using npm install react-native-wifi-reborn --save I can get the list but only in the log. How can I display the Wifi List as text from the console log.
I tried this code.
But got this error
Error: Exception in HostFunction: Malformed calls from JS: field sizes are different.
As per the docs,loadWifiList() returns Promise<Array<WifiEntry>>, so you should either use async/await or .then to access the result.
You could try something like this
const getWifiList = async() => {
let wifiList = await wifi.loadWifiList(); //wifiList will be Array<WifiEntry>
console.log('wifi list',wifiList);
}

Unit Test with Jest gives TypeError: File.test.js: Cannot read property 'toString' of undefined

I have been working in React Native using Expo CLI and recently started to face issue with my Unit tests that got failed because of one common reason. Stack trace is below
Cannot read property 'toString' of undefined
at Converter.toBase64 (node_modules/convert-source-map/index.js:61:46)
at Converter.toComment (node_modules/convert-source-map/index.js:65:21)
at generateCode (node_modules/#babel/core/lib/transformation/file/generate.js:78:76)
at run (node_modules/#babel/core/lib/transformation/index.js:55:33)
at run.next (<anonymous>)
at transform (node_modules/#babel/core/lib/transform.js:27:41)
at transform.next (<anonymous>)
at evaluateSync (node_modules/gensync/index.js:244:28)
at sync (node_modules/gensync/index.js:84:14)
My node version is node:12.18.4.I wonder what caused these errors since everything was working perfectly. On my local system they are working fine, occasionally but CI process tends to fail them randomly which hinders the overall code coverage figures.
Unit test I am trying to run is very simple as written below
it('Renders Strings as expected', () => {
expect(received).toStrictEqual(expected)
})
For those of you who are still wandering around to find answer to above question.
Issue was in the library itself convert-source-map which needed to handle this exception.
I forked the actual repository and handled that exception in line 64 toBase64 method. Now the method looks like something
Converter.prototype.toBase64 = function () {
var json = this.toJSON();
return (SafeBuffer.Buffer.from(json, 'utf8') || "").toString('base64');
};
Now everything is working fine.
Original method was something like this
Converter.prototype.toBase64 = function () {
var json = this.toJSON();
return SafeBuffer.Buffer.from(json, 'utf8').toString('base64');
};

Unable to access the url of a page in NightwatchJS

I have not been able to get the url during the test runs intermittently.
Let me give you the background: On clicking of a button on a page it navigates to the next page. Upon navigation first thing I would like to validate the url during my test.
Below is the snippet of code I have been using to fetch the url from the current page.
1. Native implementation of nightwatch for the url assertion
assertUrlContains(text) {
this.assert.urlContains(text);
return this;
}
2. Also, tried to fetch the url using the api.url with promise pattern.
getCurrentUrl() {
return new Promise((resolve) => {
this.api.url((result) => {
resolve(result.value);
});
});
}
Both of the approach have same issues and below is the stacktrace of the error:
SEARCH RESULTS URL: null
(node:12375) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Cannot read property 'indexOf' of null
(node:12375) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
✖ TypeError: Cannot read property 'indexOf' of null
Something strange I've noticed that the browser navigate to the target page but the nightwatch unable to get the url even though the page load event completes.
You may add an explicit wait condition after you click the button to ensure that the required page has successfully loaded. You may then use one of the following approaches to validate the URL:
Use the urlEquals API for nightwatch documented here: http://nightwatchjs.org/api#assert-urlEquals
You could execute a javascript command through your test script: window.location.href to retrieve the url of the window and save it as a string. You may then perform the assertion on this string

How to initilaze react-native on the view of window manager from android

I'm searching the way to implement running react-native application on both of activity and service in single package.
Running RN app on activity is usual, but can't find the right way to execute another react-native application on the service. The latter app should be run on the foreground service and view on the window manager.
My code roughly looks like:
// ReactNativeWindowService.kt
fun startNewReactWindow(
reactContext: ReactContext,
jsMainModuleName: String?,
bundleAssetName: String?,
module: String
) {
Toast.makeText(reactContext, "START()", Toast.LENGTH_SHORT).show()
val headLayout = createHeadLayout(reactContext)
headsManager.addLayout(headLayout)
launch(UI) {
reactInstanceManager = createReactInstanceManager(reactContext, jsMainModuleName, bundleAssetName)
reactRootView = ReactRootView(reactContext)
reactRootView?.startReactApplication(reactInstanceManager, module, null)
headsManager.addHeadView(bubbleLayout.apply { addView(reactRootView) })
}
}
But, this code produce:
07-10 01:05:07.662 26269-26380/Sample E/ReactNativeJS: Error while starting app: TypeError: undefined is not an object (evaluating 'NativeReactModule.startApp')
07-10 01:05:07.674 26269-26380/Sample I/ReactNativeJS: Running application "index" with appParams: {"rootTag":21}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF
07-10 01:05:07.674 26269-26380/Sample E/ReactNativeJS: Application index has not been registered.
Hint: This error often happens when you're running the packager (local dev server) from a wrong folder. For example you have multiple apps and the packager is still running for the app you were working on before.
If this is the case, simply kill the old packager instance (e.g. close the packager terminal window) and start the packager in the correct app folder (e.g. cd into app folder and run 'npm start').
This error can also happen due to a require() error during initialization or failure to call AppRegistry.registerComponent.
I'm sure that code passed right parameters such as jsMainModuleName to create ReactInstanceManager. (I got same parameters as activity's params)
What do I miss in my code? or any other reference/docs/advice that would be helpful to me. Thanks in advance. :)
You have to make sure that your file MainAcitivity.java has a function
protected String getMainComponentName() {
return "your file name for example abc";
}
But in your index.android.js file you register other component
AppRegistry.registerComponent('123', () => 143);
Where it must be like this e.g.
AppRegistry.registerComponent('abc', () => abc);
Try to fix it.

how to catch uncaught exception (globally) in react native

Does anyone know what is the best way to catch all uncaught exception (globally) so that I can send a crash report back to the server? I don't seem to be able to find any information on the react native docs or on github.
You could possibly override the exception logging that React Native uses for development:
ErrorUtils.setGlobalHandler(function() {
// your handler here
});
https://github.com/facebook/react-native/blob/522fd33d6f3c8fb339b0dde35b05df34c1233306/Libraries/JavaScriptAppEngine/Initialization/InitializeJavaScriptAppEngine.js#L46
You may then need to write some Obj-C which you expose to JS, depending on your exact requirements.
This is how I'd do it:
Step 1: We intercept react-native error handler like so:
//intercept react-native error handling
if (ErrorUtils._globalHandler) {
this.defaultHandler = ErrorUtils.getGlobalHandler && ErrorUtils.getGlobalHandler() || ErrorUtils._globalHandler;
ErrorUtils.setGlobalHandler(this.wrapGlobalHandler); //feed errors directly to our wrapGlobalHandler function
}
Step 2: Now our wrapGlobalHandler gets called whenever theres an unhandled error. So do anything you want with the error within this function.
Then do something with the error:
async function wrapGlobalHandler(error, isFatal){
const stack = parseErrorStack(error);
//do anything with the error here
this.defaultHandler(error, isFatal); //after you're finished, call the defaultHandler so that react-native also gets the error
}
Full code here:
import stacktraceParser from 'stacktrace-parser';
const parseErrorStack = (error) => {
if (!error || !error.stack) {
return [];
}
return Array.isArray(error.stack) ? error.stack :
stacktraceParser.parse(error.stack);
};
// intercept react-native error handling
if (ErrorUtils._globalHandler) {
this.defaultHandler = (ErrorUtils.getGlobalHandler
&& ErrorUtils.getGlobalHandler())
|| ErrorUtils._globalHandler;
ErrorUtils.setGlobalHandler(this.wrapGlobalHandler); // feed errors directly to our wrapGlobalHandler function
}
async function wrapGlobalHandler(error, isFatal) {
const stack = parseErrorStack(error);
//do anything with the error here
this.defaultHandler(error, isFatal); //after you're finished, call the defaultHandler so that react-native also gets the error
}
Thats it!
You can try https://github.com/master-atul/react-native-exception-handler.
A react native module that lets you to register a global error handler that can capture fatal/non fatal uncaught exceptions. The module helps prevent abrupt crashing of RN Apps without a graceful message to the user.
There is a native way.
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_scriptURL
moduleProvider:^{
id<RCTExceptionsManagerDelegate> customDelegate = ...
return #[[RCTExceptionsManager initWithDelegate:customDelegate];
}
launchOptions:nil];
Just put your report logic in the customDelegate.
There's now react-native-error-reporter, which pretty much does the trick in a very simple way:
npm i react-native-error-reporter --save
rnpm link
Then add this lines to your code:
import ErrorReporter from 'react-native-error-reporter';
ErrorReporter.init("vanson#vanportdev.com", "My App's Crash Report");
In case you're using Redux, you might wanna try redux-catch middleware.