14. Cross-platform runtimes
A cross-platform framework (React Native, later Flutter) runs the app’s code in its own runtime on top of the native platform. A conforming SDK for such a framework is a layer over the native SDKs: the native SDK sends the records, keeps the session, captures native crashes and instruments the platform’s network stack. The layer adds what only the runtime sees: its uncaught errors, their stacks, and screen names from the framework’s navigation.
This section covers JavaScript under React Native. A layer MUST expose the API of section 13 with the same behavior.
14.1 Errors and crashes
Section titled “14.1 Errors and crashes”- An error the app records, an uncaught error that does not end the process, and an unhandled
promise rejection are handled errors (6.2):
log records at severity
ERRORwithexception.type,exception.messageand the JS stack of 14.2 inexception.stacktrace. - An uncaught error that ends the process (React Native’s fatal errors in a release build)
is a crash (6.1): an
app.crashrecord sent on the next launch, with the JS stack andapsio.crash.imagesset to[]. A fatal error that does not end the process (React Native’s development builds show it and keep running) is a handled error. - An error thrown while rendering that no error boundary caught follows the framework’s own
handling, which may bypass the uncaught-error handler: React Native 0.80 and later report it as
fatal, so it is a crash; a framework that reports it as non-fatal makes it a handled error.
Either way it carries
apsio.js.mechanismrenderand the component stack. - The layer MUST record the crash synchronously in its uncaught-error handler,
through the native SDK’s
recordHybridCrash(type, message, stacktrace, attributes), before handing the error to the handler that was installed before it: the framework’s own handler terminates the process right after. The record is written to disk before the call returns. - The layer MUST call the previously installed handler for every error, after recording it, and MUST NOT let a failure of its own stop that call.
- The framework then raises a native fatal error for the same failure (React Native’s
RCTFatalExceptionon iOS, aJavascriptExceptionon Android). The native SDK MUST NOT report it as a second crash: a native crash of the same session at or after the hybrid crash’s time minus one second is the same crash. - Records from the JS runtime carry these attributes:
| Attribute | Type | Required | Value |
|---|---|---|---|
apsio.js.engine |
string | MUST | hermes, or other for any other engine. Marks exception.stacktrace as a JS stack |
apsio.js.mechanism |
string | SHOULD | manual (recorded by the app), global_handler (an uncaught error), unhandled_rejection or render (a rendering error no error boundary caught) |
apsio.js.bundle_id |
string | MAY | An identifier of the running JS bundle that the app sets when the bundle does not ship inside the binary (an over-the-air update) |
apsio.js.component_stack |
string | MAY | The framework’s component stack for a rendering error, as text, with every location reduced like a frame’s <file> (14.2) |
A backend MUST treat exception.stacktrace as a JS stack when, and only when,
apsio.js.engine is present.
14.2 JS stacks
Section titled “14.2 JS stacks”exception.stacktrace holds the frames only, innermost first, one per line, in Hermes’
syntax with a four-space indent. The error’s type and message are in exception.type and
exception.message.
| Line | Frame |
|---|---|
at <function> (address at <file>:<segment>:<offset>) |
Bytecode: a 1-based segment (1 for a bundle that is not split) and the 0-based bytecode offset |
at <function> (<file>:<line>:<column>) |
Source: a 1-based line and 1-based column in the bundle’s JavaScript |
at <function> (native) |
A native function, with no location |
... skipping <n> frames |
Frames the engine left out |
Caused by: <type>: <message> |
The start of the error’s cause (Error.cause), whose frames follow; the message on one line |
<file>is the last path component of the frame’s source URL, without query or fragment:main.jsbundle,index.android.bundle,index.bundle(a development server). Full URLs hold per-install container paths and development hosts.<function>is the name the engine printed (it may contain spaces and dots), oranonymous.- An SDK MUST rewrite frames of another engine to the source form, MUST leave out any line that is not a frame, and MUST send at most 200 frames per error and at most five causes.
Example of a crash from a release build:
at construct (native) at CheckoutError (address at main.jsbundle:1:794126) at crashCheckout (address at main.jsbundle:1:454705)14.3 Source maps
Section titled “14.3 Source maps”Release builds compile the bundle to Hermes bytecode and compose the bundler’s source map with the compiler’s into one map, whose generated positions are the bytecode offsets on line
- A backend symbolicates JS frames with the map uploaded for the bundle:
- A record with
apsio.js.bundle_idis symbolicated only with the map uploaded for that bundle id. When there is none yet, its frames stay unresolved until it arrives; a backend MUST NOT use the build’s map instead, since an over-the-air bundle’s bytecode differs from the bundle the build shipped and that map would resolve its frames to wrong functions and lines. - A record without
apsio.js.bundle_idis symbolicated with the map uploaded for itsapp.build_id(2.1, after 2.5) and the bundle’s file name, the<file>of the frames. - A bytecode frame resolves at line 1, column
<offset>of the composed map; a source frame at line<line>, column<column> - 1of the bundler’s map. Frames inInternalBytecode.jsand(native)frames are not resolved.
14.4 Network and screens
Section titled “14.4 Network and screens”- Requests made from JavaScript go through the platform’s HTTP stack, where the native SDK records them as client spans with the rules of 8.4. A layer MUST NOT record them a second time from JavaScript.
- Screens come from the framework’s navigation (React Navigation, Expo Router) through
trackScreen(8.1). A layer SHOULD turn the native SDK’s automatic screens off, since the framework’s native containers are not the app’s screens, and SHOULD name screens by route, never by values such as ids. - The native SDK SHOULD be started natively, at process start, before the framework
loads: a start from the runtime comes after the launch, so it cannot measure
app.start(8.1) or capture a crash that happens before the bundle runs.