@everframe/react-native 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/EverframeRN.podspec +1 -1
- package/README.md +66 -37
- package/android/build.gradle.kts +1 -1
- package/android/src/main/java/dev/everframe/rn/EverframeModule.kt +37 -36
- package/android/src/main/java/dev/everframe/rn/ReporterBridge.kt +4 -13
- package/dist/NativeEverframe.d.ts +20 -5
- package/dist/chunk-MIWWEGLO.js +1 -0
- package/dist/chunk-UN3Q4UGZ.js +1 -0
- package/dist/index.d.ts +102 -100
- package/dist/index.js +23 -23
- package/dist/index.web.d.ts +2 -2
- package/dist/index.web.js +1 -1
- package/dist/integrations/console.js +1 -1
- package/dist/integrations/react-navigation.js +1 -1
- package/dist/integrations/react.js +1 -1
- package/dist/{promise-rejection-types-C-lxCdDl.d.ts → promise-rejection-types-CiyBTz7v.d.ts} +7 -9
- package/ios/Sources/EverframeBridge.swift +53 -14
- package/ios/Sources/EverframeEventEmitter.swift +3 -5
- package/ios/Sources/EverframeModule.mm +17 -16
- package/package.json +4 -4
- package/src/NativeEverframe.ts +29 -29
- package/src/codegen-types.ts +0 -1
- package/dist/chunk-ENPQ66EV.js +0 -1
- package/dist/chunk-POE5CSO5.js +0 -1
package/EverframeRN.podspec
CHANGED
|
@@ -40,7 +40,7 @@ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
|
40
40
|
# 1.2.0, so the range already asks for 1.2 while package.json is still 1.1.0.
|
|
41
41
|
# The 1.2.0 release moves package.json to match.
|
|
42
42
|
native_version_override = ENV['EVERFRAME_NATIVE_POD_VERSION'].to_s.strip
|
|
43
|
-
native_minor_range = native_version_override.empty? ? '~> 1.
|
|
43
|
+
native_minor_range = native_version_override.empty? ? '~> 1.3.0' : native_version_override
|
|
44
44
|
|
|
45
45
|
Pod::Spec.new do |s|
|
|
46
46
|
s.name = 'EverframeRN'
|
package/README.md
CHANGED
|
@@ -29,6 +29,25 @@ Then install pods (iOS) / sync Gradle (Android) as usual:
|
|
|
29
29
|
cd ios && pod install
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
### Source maps and symbols
|
|
33
|
+
|
|
34
|
+
Release builds can upload their Hermes source maps, dSYMs, R8 mappings and
|
|
35
|
+
native libraries, so crashes show your code. Wrap Metro with
|
|
36
|
+
`withEverframe` from `@everframe/metro`, add `@everframe/cli`, and set up the
|
|
37
|
+
native projects once. In Expo, apply `withEverframe` from `@everframe/expo`
|
|
38
|
+
with your App ID in `app.config.js`. In a bare app, run:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx @everframe/cli setup react-native --app-id "$EVERFRAME_APP_ID"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Either way, Android gets `apply plugin: "dev.everframe"` and
|
|
45
|
+
`everframe { appId = "…" }` plus one classpath line, and Xcode gets a
|
|
46
|
+
one-line Run Script phase that runs `scripts/xcode-upload.sh` from the installed
|
|
47
|
+
`@everframe/cli`. The upload logic lives in the Gradle plugin and the CLI. Set `EVERFRAME_API_TOKEN` in release builds;
|
|
48
|
+
without it the build warns and continues. See the
|
|
49
|
+
[CLI guide](../cli/README.md#react-native-and-expo).
|
|
50
|
+
|
|
32
51
|
## React Native Web
|
|
33
52
|
|
|
34
53
|
`@everframe/react-native` has a browser entry and an explicit
|
|
@@ -49,6 +68,8 @@ the web mask registry. Native iOS and Android use their native capture. A web
|
|
|
49
68
|
bundler that honors the `browser` export condition can keep the regular package
|
|
50
69
|
import; use the `/web` subpath if its resolver does not select that condition.
|
|
51
70
|
|
|
71
|
+
Replays also attach to errors; see [session replay](../../docs/session-replay.md).
|
|
72
|
+
|
|
52
73
|
## Usage
|
|
53
74
|
|
|
54
75
|
Wrap your app with `<EverframeProvider>` at the highest practical level
|
|
@@ -283,15 +304,12 @@ Snapshots are detached, contain no captured content, reset on remount, and perfo
|
|
|
283
304
|
no native query or upload. Paths are `handled` (including the boundary callback),
|
|
284
305
|
`errorUtils` (including errors no boundary catches), and `rejection` (submission only). Each active attempt has one outcome:
|
|
285
306
|
`accepted`, `duplicateSuppressed`, `allowanceSuppressed`, `bridgeUnavailable`,
|
|
286
|
-
`nativeRefused`, `captureFailed`, `reentrantSuppressed`, `inactiveAborted
|
|
287
|
-
|
|
307
|
+
`nativeRefused`, `captureFailed`, `reentrantSuppressed`, or `inactiveAborted`.
|
|
308
|
+
Counters saturate at 2,147,483,647. Existing limits are ten
|
|
288
309
|
handled and ten automatic distinct keys; fatal capture bypasses them.
|
|
289
310
|
|
|
290
|
-
`accepted` requires exactly true from
|
|
291
|
-
not mean upload success, server processing or outbox removal.
|
|
292
|
-
to an older native fallback is only `legacyAttempted`, regardless of its return.
|
|
293
|
-
Old-binary unavailable/throwing methods are covered by unit controls, not an
|
|
294
|
-
installed old-binary qualification. This snapshot excludes native queue and
|
|
311
|
+
`accepted` requires exactly true from the native capture method. It does
|
|
312
|
+
not mean upload success, server processing or outbox removal. This snapshot excludes native queue and
|
|
295
313
|
delivery observations; read those with `getReportDeliveryStatus()`. Status is
|
|
296
314
|
neutral `not-mounted` without a provider, `disabled` under the crash-reporting
|
|
297
315
|
veto, and `unsupported` on the browser root entry.
|
|
@@ -341,8 +359,7 @@ bridges. iOS marks `jvm-uncaught` unsupported and implies no automatic iOS nativ
|
|
|
341
359
|
crash collector.
|
|
342
360
|
|
|
343
361
|
Without a mounted provider the result is `not-mounted/no-mount`; the browser
|
|
344
|
-
export is `unsupported/platform`.
|
|
345
|
-
`unsupported/native-method-missing`. A throwing native call returns
|
|
362
|
+
export is `unsupported/platform`. A missing or throwing native call returns
|
|
346
363
|
`unavailable/native-call-failed`, and malformed native responses return
|
|
347
364
|
`unavailable/invalid-native-snapshot`. These fallbacks have an unobserved queue
|
|
348
365
|
and `unknown` platform policies. This API does not provide a per-report delivery
|
|
@@ -379,6 +396,34 @@ capturing network traffic in the first place:
|
|
|
379
396
|
Do not enable this option expecting RN's own network calls to show up in
|
|
380
397
|
reports — that capability doesn't exist yet.
|
|
381
398
|
|
|
399
|
+
## Crash-only sessions
|
|
400
|
+
|
|
401
|
+
```tsx
|
|
402
|
+
<EverframeProvider config={{ sdkKey: '…', reporter: { disabled: true } }}>
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
`reporter.disabled: true` turns the bug reporter off for the session and
|
|
406
|
+
keeps crash and error reporting, for example in production builds. It is a
|
|
407
|
+
**client veto**: the dashboard can't turn the reporter back on for this
|
|
408
|
+
session.
|
|
409
|
+
|
|
410
|
+
In a crash-only session:
|
|
411
|
+
|
|
412
|
+
- Shake-to-report is off, and `open()` rejects with
|
|
413
|
+
`EverframeReporterDisabledError`.
|
|
414
|
+
- Session replay uses the app's crash-only sampling rate, which is 0 by
|
|
415
|
+
default. An error carries a replay only while the session records.
|
|
416
|
+
- Network bodies aren't captured.
|
|
417
|
+
- Crash and error capture, breadcrumbs, vitals, release health and the
|
|
418
|
+
companion work as usual.
|
|
419
|
+
|
|
420
|
+
A companion bond (a phone scanning the QR code, or a dashboard member
|
|
421
|
+
attaching) upgrades the session. Replay and network bodies then follow the
|
|
422
|
+
app's normal settings for the rest of the session, and recording starts at
|
|
423
|
+
the bond. The reporter stays off: reports from an upgraded session come
|
|
424
|
+
through the companion. See
|
|
425
|
+
[crash-only sessions](../../docs/session-replay.md#crash-only-sessions).
|
|
426
|
+
|
|
382
427
|
## Navigation breadcrumbs
|
|
383
428
|
|
|
384
429
|
Mark screens with one line; the native SDK derives `from → to` from a global
|
|
@@ -472,15 +517,15 @@ inside a native view and never reaches JS.
|
|
|
472
517
|
- `captureSourceQuery` — keep the query string on `source_change.src`
|
|
473
518
|
(default `false`: signed CDN URLs carry tokens in the query).
|
|
474
519
|
|
|
475
|
-
**Reconfiguring.** A Provider remount with the
|
|
476
|
-
the SDK: both native sides compare the incoming config against the
|
|
520
|
+
**Reconfiguring.** A Provider remount with the _same_ config does not restart
|
|
521
|
+
the SDK: both native sides compare the incoming config against the _installed_
|
|
477
522
|
one and skip the start entirely. A **changed** config does restart it (on iOS,
|
|
478
523
|
except a change confined to `releaseHealth` or the loaded bundle; see
|
|
479
524
|
[Release health](#release-health)) — and a
|
|
480
525
|
start supersedes the running SDK, detaching every player integration it had
|
|
481
526
|
announced. **Limitation:** players tracked before that restart stay untracked
|
|
482
527
|
until their screens remount; nothing re-registers them automatically. Two React
|
|
483
|
-
instances in one process are
|
|
528
|
+
instances in one process are _not_ coordinated either: if a second one starts
|
|
484
529
|
the SDK under the first, the first instance's players stay untracked until its
|
|
485
530
|
own screens remount.
|
|
486
531
|
|
|
@@ -525,7 +570,7 @@ adapter has subscribed — and `onLoadStart` is what arms the adapter's startup
|
|
|
525
570
|
clock, so the first source's startup latency is simply never seen. Deferring
|
|
526
571
|
the load until after the adapter subscribed puts the first `onLoadStart` back
|
|
527
572
|
where it can be observed. Without this, everything else still works; only the
|
|
528
|
-
|
|
573
|
+
_first_ `startup` measurement is missing (later source changes are fine).
|
|
529
574
|
|
|
530
575
|
Options: `name`, `libraryVersion`, and `captureErrors` (default `false` — see
|
|
531
576
|
Limitations). `onBandwidthUpdate` is read per platform: on iOS its `bitrate`
|
|
@@ -535,7 +580,7 @@ the `bandwidthEstimate` stat (with `quality_change` for the accompanying
|
|
|
535
580
|
rendition size) and never as a bitrate.
|
|
536
581
|
|
|
537
582
|
`onProgress` is read per platform too. The `bufferAheadMs` stat is always the
|
|
538
|
-
media time buffered
|
|
583
|
+
media time buffered _ahead of the playhead_: on Android `bufferDuration` is
|
|
539
584
|
already that, on iOS it is the buffered range's absolute end position, so the
|
|
540
585
|
adapter subtracts `currentTime` from it.
|
|
541
586
|
|
|
@@ -618,7 +663,7 @@ dropped, not forwarded.
|
|
|
618
663
|
`captureErrors` is off by default.
|
|
619
664
|
- **`rate_change` was not observed from react-native-video v7 on Android** in
|
|
620
665
|
our Android emulator smoke — `onPlaybackRateChange` never arrived there.
|
|
621
|
-
Note that the library
|
|
666
|
+
Note that the library _does_ forward ExoPlayer's
|
|
622
667
|
`onPlaybackParametersChanged`, so this is a "not seen in our smoke run"
|
|
623
668
|
result, not a proven library gap; a host that changes playback speed
|
|
624
669
|
explicitly may well see it. Nothing on the adapter side gates it either way.
|
|
@@ -693,7 +738,7 @@ subscription?.remove();
|
|
|
693
738
|
```
|
|
694
739
|
|
|
695
740
|
> If you have seen the older `new TVEventHandler(); handler.enable(cmp, cb);
|
|
696
|
-
|
|
741
|
+
handler.disable()` shape elsewhere — that class-based API was removed in
|
|
697
742
|
> RN-tvos 0.85.x. Always read the version installed in your `node_modules`.
|
|
698
743
|
|
|
699
744
|
#### Apple TV: long-press Play/Pause
|
|
@@ -777,28 +822,12 @@ recipes above.
|
|
|
777
822
|
## Android reporter initialization
|
|
778
823
|
|
|
779
824
|
The Android reporter can be opened after the host activity has resumed, including
|
|
780
|
-
apps that disable AndroidX Startup.
|
|
781
|
-
`dev.everframe:reporter-ui` artifacts: the bridge supplies the current resumed
|
|
825
|
+
apps that disable AndroidX Startup. The bridge supplies the current resumed
|
|
782
826
|
activity, and installation and presentation run on the main thread. Repeated
|
|
783
|
-
configuration
|
|
784
|
-
remains synchronous without waiting for reporter UI initialization.
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
for compiled callers. A new bridge with an older reporter uses its legacy entry
|
|
788
|
-
point when the activity-aware method is absent; that combination retains the old
|
|
789
|
-
late-initialization limitation. A failure from an available method is reported,
|
|
790
|
-
not retried through the legacy path. Background or unavailable activities return
|
|
791
|
-
a cancelled result with reason `no_active_activity`.
|
|
792
|
-
|
|
793
|
-
Legacy off-main `create(Context)` schedules installation and returns before it is
|
|
794
|
-
ready; use the awaited activity-aware bridge to open immediately after configure.
|
|
795
|
-
With a new bridge and an older reporter, each configure, companion start, and
|
|
796
|
-
open invokes legacy installation again. This can add duplicate lifecycle
|
|
797
|
-
callbacks and overwrite that older reporter's resolver and activity supplier;
|
|
798
|
-
the previous bridge limited installation to once per module. Conversely, an old
|
|
799
|
-
compiled bridge with the new reporter can announce companion PIN capability
|
|
800
|
-
before asynchronous reporter installation finishes, temporarily advertising no
|
|
801
|
-
PIN support. Use the matching updated pair for the qualified behavior.
|
|
827
|
+
configuration does not register another lifecycle observer. Capture configuration
|
|
828
|
+
remains synchronous without waiting for reporter UI initialization. A failure
|
|
829
|
+
while opening is reported to JavaScript. Background or unavailable activities
|
|
830
|
+
return a cancelled result with reason `no_active_activity`.
|
|
802
831
|
|
|
803
832
|
Activity destruction while a reporter is open resolves the pending call with
|
|
804
833
|
`{ status: "cancelled", reason: "activity_destroyed" }`, releases that report's
|
package/android/build.gradle.kts
CHANGED
|
@@ -153,7 +153,7 @@ val rnSdkPkg = groovy.json.JsonSlurper()
|
|
|
153
153
|
val rnSdkVersion = rnSdkPkg["version"] as String
|
|
154
154
|
// Native 1.2.0 is the release that renamed the SDK key to `sdkKey`; the
|
|
155
155
|
// bridge moves with it, ahead of package.json, which the 1.2.0 release bumps.
|
|
156
|
-
val nativeMinorRange: String = nativeVersionOverride ?: "[1.
|
|
156
|
+
val nativeMinorRange: String = nativeVersionOverride ?: "[1.3.0,1.4.0)"
|
|
157
157
|
if (nativeVersionOverride != null) {
|
|
158
158
|
logger.lifecycle(
|
|
159
159
|
"[everframe-rn] native AAR pinned to $nativeVersionOverride via everframeNativeVersion " +
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
// signalCompanionReportRequestReady(id) → void (Plan 06.2-13)
|
|
23
23
|
// trackPlayer/detachPlayer/recordPlayerEvent/updatePlayerStats/
|
|
24
24
|
// trackVitals → void (spec 2026-09-06)
|
|
25
|
-
//
|
|
25
|
+
// configureSync(opts.vitals*) → boolean (spec 2026-09-06)
|
|
26
26
|
// addListener(name)/removeListeners(n) → void (RN event-emitter no-ops)
|
|
27
27
|
//
|
|
28
28
|
// The native TXReporterPresenter owns the entire report UX (capture, annotate,
|
|
@@ -30,8 +30,7 @@
|
|
|
30
30
|
// and the sensitive-rect bridge. Native mobile shake stays below this bridge.
|
|
31
31
|
//
|
|
32
32
|
// THREADING:
|
|
33
|
-
// • configureSync runs on the JS thread (blocking synchronous)
|
|
34
|
-
// void configure retains React Native's queued native dispatch.
|
|
33
|
+
// • configureSync runs on the JS thread (blocking synchronous).
|
|
35
34
|
// • openReporter dispatches presenter.openReporter onto the activity from
|
|
36
35
|
// MainScope (presenter manages its own UI thread hops internally).
|
|
37
36
|
// • registerSensitiveRect runs on the UI thread (UIManager view resolution).
|
|
@@ -148,6 +147,8 @@ class EverframeModule(
|
|
|
148
147
|
internal const val REJECT_CAPTURE_DISABLED: String = "capture_disabled"
|
|
149
148
|
internal const val REJECT_NO_ACTIVITY: String = "no_activity"
|
|
150
149
|
internal const val REJECT_OPEN_REPORTER_FAILED: String = "open_reporter_failed"
|
|
150
|
+
/** Crash-only sessions (spec 2026-10-12). The JS facade maps this exact code to `EverframeReporterDisabledError`. */
|
|
151
|
+
internal const val REJECT_REPORTER_DISABLED: String = "EVERFRAME_REPORTER_DISABLED"
|
|
151
152
|
|
|
152
153
|
// Plan 06.2-11 — JS-side event names. Keep in lock-step with
|
|
153
154
|
// `packages/sdk-react-native/src/companion.ts` and the iOS
|
|
@@ -175,13 +176,10 @@ class EverframeModule(
|
|
|
175
176
|
// below for the full contract.
|
|
176
177
|
internal const val COMPANION_ATTACH_CHALLENGE_EVENT: String =
|
|
177
178
|
"everframe.companion.attachChallenge"
|
|
178
|
-
// Fired when a `report.request` arrives from the paired phone.
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
// `signalCompanionReportRequestReady` acknowledgement survive as the
|
|
183
|
-
// companion's JS-ready handshake — the bounded wait is part of the
|
|
184
|
-
// pairing flow, not of tap-to-identify.
|
|
179
|
+
// Fired when a `report.request` arrives from the paired phone. The
|
|
180
|
+
// event and its `signalCompanionReportRequestReady` acknowledgement
|
|
181
|
+
// are the companion's JS-ready handshake — the bounded wait is part
|
|
182
|
+
// of the pairing flow.
|
|
185
183
|
internal const val COMPANION_REPORT_REQUESTED_EVENT: String = "everframe.companion.reportRequested"
|
|
186
184
|
|
|
187
185
|
// Spec 2026-09-17 setExtra-resolver — NOT a companion event (no
|
|
@@ -209,11 +207,6 @@ class EverframeModule(
|
|
|
209
207
|
|
|
210
208
|
// ---------------- configure ----------------
|
|
211
209
|
|
|
212
|
-
@ReactMethod
|
|
213
|
-
override fun configure(opts: ReadableMap) {
|
|
214
|
-
configureInternal(opts)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
210
|
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
218
211
|
override fun configureSync(opts: ReadableMap): Boolean = configureInternal(opts)
|
|
219
212
|
|
|
@@ -271,6 +264,10 @@ class EverframeModule(
|
|
|
271
264
|
// Polarity flips exactly once, here: JS sends the veto
|
|
272
265
|
// (`crashReportingDisabled`), native stores the permission.
|
|
273
266
|
val crashReportingDisabled = opts.takeIfHasBoolean("crashReportingDisabled") ?: false
|
|
267
|
+
// Polarity flips exactly once, here: JS sends the crash-only switch
|
|
268
|
+
// (`reporterDisabled`, flattened from `reporter: { disabled }`),
|
|
269
|
+
// native stores the permission.
|
|
270
|
+
val reporterDisabled = opts.takeIfHasBoolean("reporterDisabled") ?: false
|
|
274
271
|
// Attach-PIN UI mode (spec 2026-08-19) — a flat optional STRING
|
|
275
272
|
// field (codegen cannot express a string-literal union), parsed
|
|
276
273
|
// here and stashed for `startCompanion()` to read (the flag is a
|
|
@@ -314,6 +311,7 @@ class EverframeModule(
|
|
|
314
311
|
// `startCompanion()` time, mirroring `attachPinUi`.
|
|
315
312
|
companionBadgeEnabled = opts.takeIfHasBoolean("companionBadgeEnabled") ?: true,
|
|
316
313
|
shakeToReportEnabled = opts.takeIfHasBoolean("shakeToReportEnabled") ?: true,
|
|
314
|
+
reporterEnabled = !reporterDisabled,
|
|
317
315
|
companionBadgePosition = opts.takeIfHasString("companionBadgePosition"),
|
|
318
316
|
// Inline reporter theme (reporter branding spec 2026-08-25,
|
|
319
317
|
// RN slice) — 8 flat optional STRING fields on ConfigOpts
|
|
@@ -423,6 +421,9 @@ class EverframeModule(
|
|
|
423
421
|
* { status: "submitted", reportId: "<uuid>" }
|
|
424
422
|
* { status: "queued", reportId: "<uuid>" }
|
|
425
423
|
* { status: "cancelled" }
|
|
424
|
+
*
|
|
425
|
+
* Rejects with [REJECT_REPORTER_DISABLED] in a crash-only session
|
|
426
|
+
* (`reporterDisabled`), including after a companion bond upgraded it.
|
|
426
427
|
*/
|
|
427
428
|
@ReactMethod
|
|
428
429
|
override fun openReporter(promise: Promise) {
|
|
@@ -430,6 +431,12 @@ class EverframeModule(
|
|
|
430
431
|
promise.reject(REJECT_CAPTURE_DISABLED, "Everframe not started (captureGate=false). Call configure() first.")
|
|
431
432
|
return
|
|
432
433
|
}
|
|
434
|
+
// Crash-only session: rejected here because ReporterBridge reduces every core failure to one
|
|
435
|
+
// generic message, which would hide `ReporterDisabledException` from JS.
|
|
436
|
+
if (Everframe.currentConfig?.reporterEnabled == false) {
|
|
437
|
+
promise.reject(REJECT_REPORTER_DISABLED, "The reporter is turned off for this session (reporter.disabled).")
|
|
438
|
+
return
|
|
439
|
+
}
|
|
433
440
|
MainScope().launch {
|
|
434
441
|
try {
|
|
435
442
|
val result: ReportResult = reporterBridge.open(reactContext)
|
|
@@ -500,7 +507,7 @@ class EverframeModule(
|
|
|
500
507
|
* `Everframe.addBreadcrumb` (Task 9) — NO validation/coercion here. That
|
|
501
508
|
* singleton already owns kind/level/data coercion AND gating (no-op
|
|
502
509
|
* pre-start / while killed via its own `txGuardVoid` + `captureGate`
|
|
503
|
-
* check), exactly like `setExtra
|
|
510
|
+
* check), exactly like `setExtra` above never re-checks
|
|
504
511
|
* `captureGate` themselves. `data` crosses as a `ReadableMap?` — the
|
|
505
512
|
* codegen-typed freeform-object boundary — converted with RN's own
|
|
506
513
|
* `toHashMap()` (no re-shaping) before handing off.
|
|
@@ -637,7 +644,7 @@ class EverframeModule(
|
|
|
637
644
|
*
|
|
638
645
|
* The primary fix is the JS projection in `src/user-projection.ts`, applied
|
|
639
646
|
* before the value ever crosses the bridge. This is the second layer, for a
|
|
640
|
-
* hand-wired native caller
|
|
647
|
+
* hand-wired native caller that reaches
|
|
641
648
|
* this method directly: bad fields degrade to `null` — a partial user, or
|
|
642
649
|
* anonymous — and `Everframe.setUser` is ALWAYS reached, so the call always
|
|
643
650
|
* replaces what was installed before.
|
|
@@ -687,11 +694,11 @@ class EverframeModule(
|
|
|
687
694
|
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
688
695
|
override fun getReportDeliveryStatusJson(): String = Everframe.getReportDeliveryStatus().toJson()
|
|
689
696
|
|
|
690
|
-
/** Automatic ErrorUtils capture; true acknowledges completed sidecar storage. */
|
|
697
|
+
/** Automatic ErrorUtils and unhandled-rejection capture; true acknowledges completed sidecar storage. A non-fatal error may carry a replay. */
|
|
691
698
|
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
692
699
|
override fun reportCrash(crashJson: String): Boolean = captureCrashJson(crashJson, handled = false)
|
|
693
700
|
|
|
694
|
-
/** Explicit capture always enforces handled=true, fatal=false, captureException. */
|
|
701
|
+
/** Explicit capture always enforces handled=true, fatal=false, captureException, and may carry a replay. */
|
|
695
702
|
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
696
703
|
override fun captureHandledException(crashJson: String): Boolean = captureCrashJson(crashJson, handled = true)
|
|
697
704
|
|
|
@@ -718,12 +725,17 @@ class EverframeModule(
|
|
|
718
725
|
val details = if (obj.has("details")) obj.get("details") else null
|
|
719
726
|
val causeChain = if (obj.has("causeChain")) obj.get("causeChain") else null
|
|
720
727
|
val fatal = !handled && obj.optBoolean("fatal", false)
|
|
721
|
-
|
|
722
|
-
|
|
728
|
+
// Only errors the app survives ask for a replay. Eligibility and export remain in the core;
|
|
729
|
+
// fatal errors keep the entry point that never carries a replay.
|
|
730
|
+
val accepted = when {
|
|
731
|
+
handled -> dev.everframe.crash.CrashReporter.captureHandledFactsWithReplay(
|
|
723
732
|
exceptionType, message, frames, occurredAt, jsBundle, details, causeChain, "everframe-android",
|
|
724
733
|
)
|
|
725
|
-
|
|
726
|
-
|
|
734
|
+
fatal -> dev.everframe.crash.CrashReporter.captureFactsAcceptedWithCauses(
|
|
735
|
+
exceptionType, message, frames, obj.optString("mechanism", "errorutils"),
|
|
736
|
+
fatal, occurredAt, jsBundle, details, causeChain,
|
|
737
|
+
)
|
|
738
|
+
else -> dev.everframe.crash.CrashReporter.captureFactsAcceptedWithReplay(
|
|
727
739
|
exceptionType, message, frames, obj.optString("mechanism", "errorutils"),
|
|
728
740
|
fatal, occurredAt, jsBundle, details, causeChain,
|
|
729
741
|
)
|
|
@@ -1097,14 +1109,6 @@ class EverframeModule(
|
|
|
1097
1109
|
val capture: ScreenshotCapture.CaptureResult,
|
|
1098
1110
|
val reportCapture: dev.everframe.capture.video.FrozenReportCapture,
|
|
1099
1111
|
val hostExtra: String?,
|
|
1100
|
-
/**
|
|
1101
|
-
* Drained-and-discarded host React tree (spec 2026-08-29). Nothing
|
|
1102
|
-
* reads it any more, and RN can no longer attach one — the
|
|
1103
|
-
* TurboModule spec method is gone. Kept only because the native core
|
|
1104
|
-
* `Everframe.attachReactTree` remains callable by pure-native hosts,
|
|
1105
|
-
* so the consume-once drain must still clear the slot.
|
|
1106
|
-
*/
|
|
1107
|
-
val hostReactTreeJson: String?,
|
|
1108
1112
|
)
|
|
1109
1113
|
|
|
1110
1114
|
private val companionCaptureStash = ConcurrentHashMap<String, CompanionCapture>()
|
|
@@ -1161,7 +1165,7 @@ class EverframeModule(
|
|
|
1161
1165
|
// same reason the screenshot capture above is —
|
|
1162
1166
|
// this whole provider runs on the OkHttp WS
|
|
1163
1167
|
// thread, never the UI thread.
|
|
1164
|
-
val
|
|
1168
|
+
val hostExtraRaw =
|
|
1165
1169
|
runBlocking { Everframe.consumePendingAttachments() }
|
|
1166
1170
|
val hostExtra = hostExtraRaw?.takeIf { it.isNotEmpty() }
|
|
1167
1171
|
|
|
@@ -1173,7 +1177,6 @@ class EverframeModule(
|
|
|
1173
1177
|
capture = capture,
|
|
1174
1178
|
reportCapture = reportCapture,
|
|
1175
1179
|
hostExtra = hostExtra,
|
|
1176
|
-
hostReactTreeJson = hostReactTreeJson?.takeIf { it.isNotEmpty() },
|
|
1177
1180
|
)
|
|
1178
1181
|
}
|
|
1179
1182
|
if (!published) {
|
|
@@ -1485,9 +1488,7 @@ class EverframeModule(
|
|
|
1485
1488
|
/**
|
|
1486
1489
|
* Called by the JS companion module from the `everframe.companion
|
|
1487
1490
|
* .reportRequested` event handler once it is ready for capture to
|
|
1488
|
-
* proceed.
|
|
1489
|
-
* `attachReactTree` first; both are gone — spec 2026-08-29.) Releases
|
|
1490
|
-
* the `awaitJsReactTreeAttach` waiter for this correlation id so the
|
|
1491
|
+
* proceed. Releases the `awaitJsReactTreeAttach` waiter for this correlation id so the
|
|
1491
1492
|
* capture provider can proceed. Idempotent — late signals after the
|
|
1492
1493
|
* timeout fired are harmless.
|
|
1493
1494
|
*/
|
|
@@ -9,7 +9,6 @@ import android.os.Looper
|
|
|
9
9
|
import android.util.Log
|
|
10
10
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
11
11
|
import com.facebook.react.common.LifecycleState
|
|
12
|
-
import dev.everframe.Everframe
|
|
13
12
|
import dev.everframe.config.ReportResult
|
|
14
13
|
import java.lang.reflect.InvocationTargetException
|
|
15
14
|
import java.lang.reflect.Method
|
|
@@ -19,7 +18,7 @@ import kotlinx.coroutines.CancellationException
|
|
|
19
18
|
import kotlinx.coroutines.Dispatchers
|
|
20
19
|
import kotlinx.coroutines.withContext
|
|
21
20
|
|
|
22
|
-
/**
|
|
21
|
+
/** Resolves the reporter installer reflectively, so tests can substitute it. */
|
|
23
22
|
internal class ReporterBridge(
|
|
24
23
|
private val resolveInstaller: () -> Class<*> = { Class.forName("dev.everframe.ui.ReporterResolverInstaller") },
|
|
25
24
|
) {
|
|
@@ -46,17 +45,9 @@ internal class ReporterBridge(
|
|
|
46
45
|
try {
|
|
47
46
|
val type = installerType()
|
|
48
47
|
val instance = type.getDeclaredConstructor().newInstance()
|
|
49
|
-
val method =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (method == null) {
|
|
53
|
-
// Absence is the only legacy fallback. A present method's failure is final.
|
|
54
|
-
invoke(type.getMethod("create", Context::class.java), instance, context.applicationContext)
|
|
55
|
-
Everframe.report.open()
|
|
56
|
-
} else {
|
|
57
|
-
suspendCoroutineUninterceptedOrReturn<ReportResult> { continuation ->
|
|
58
|
-
invoke(method, instance, context.applicationContext, activity, continuation)
|
|
59
|
-
}
|
|
48
|
+
val method = type.getMethod("openForActivity", Context::class.java, Activity::class.java, Continuation::class.java)
|
|
49
|
+
suspendCoroutineUninterceptedOrReturn<ReportResult> { continuation ->
|
|
50
|
+
invoke(method, instance, context.applicationContext, activity, continuation)
|
|
60
51
|
}
|
|
61
52
|
} catch (failure: Throwable) {
|
|
62
53
|
when (failure) {
|
|
@@ -89,6 +89,13 @@ type ConfigOpts = {
|
|
|
89
89
|
companionBadgeEnabled?: boolean;
|
|
90
90
|
/** Local veto for native mobile shake-to-report. Absent defaults to enabled. */
|
|
91
91
|
shakeToReportEnabled?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Crash-only session switch. `true` turns off every on-device way to open the reporter, keeps
|
|
94
|
+
* network bodies off and samples replay with the server's crash-only rate until a companion
|
|
95
|
+
* bond upgrades the session. Flattened by `extractBridgeConfig` from `reporter: { disabled }`.
|
|
96
|
+
* Absent means a normal report session.
|
|
97
|
+
*/
|
|
98
|
+
reporterDisabled?: boolean;
|
|
92
99
|
/**
|
|
93
100
|
* Companion on-screen name-badge corner (naming spec 2026-08-24,
|
|
94
101
|
* controller ruling / Task 6b). Flat optional STRING, not a literal
|
|
@@ -170,11 +177,10 @@ type EverframeUserSpec = {
|
|
|
170
177
|
displayName?: string;
|
|
171
178
|
};
|
|
172
179
|
interface Spec extends TurboModule {
|
|
173
|
-
configure(opts: ConfigOpts): void;
|
|
174
180
|
/**
|
|
175
|
-
* Synchronous startup entry
|
|
176
|
-
*
|
|
177
|
-
*
|
|
181
|
+
* Synchronous startup entry. It is blocking rather than void because React
|
|
182
|
+
* Native dispatches void TurboModule methods asynchronously, and the first
|
|
183
|
+
* capture after mount must not overtake configuration.
|
|
178
184
|
*/
|
|
179
185
|
configureSync(opts: ConfigOpts): boolean;
|
|
180
186
|
/**
|
|
@@ -261,6 +267,12 @@ interface Spec extends TurboModule {
|
|
|
261
267
|
* fatal, occurredAt }. Native builds/redacts/persists the envelope with
|
|
262
268
|
* state it already owns (crumb ring, device metadata, outbox).
|
|
263
269
|
* Returns true when the report was persisted.
|
|
270
|
+
*
|
|
271
|
+
* A non-fatal ErrorUtils error or unhandled promise rejection may carry a
|
|
272
|
+
* session replay of the seconds before it. A fatal error never carries one.
|
|
273
|
+
* Native decides replay eligibility; JavaScript sends no extra fields.
|
|
274
|
+
* Send `fatal` as a JSON boolean: only an absent flag or boolean false can
|
|
275
|
+
* request a replay on iOS.
|
|
264
276
|
*/
|
|
265
277
|
reportCrash(crashJson: string): boolean;
|
|
266
278
|
/** Register a remote player. A token already live is ignored. */
|
|
@@ -288,7 +300,10 @@ interface Spec extends TurboModule {
|
|
|
288
300
|
* `token` resolves to a live player, else session-scoped.
|
|
289
301
|
*/
|
|
290
302
|
trackVitals(name: string, dataJson?: string, token?: string): void;
|
|
291
|
-
/**
|
|
303
|
+
/**
|
|
304
|
+
* Explicit handled capture; native enforces handled=true, fatal=false. Like a
|
|
305
|
+
* non-fatal `reportCrash`, it may carry a session replay.
|
|
306
|
+
*/
|
|
292
307
|
captureHandledException(crashJson: string): boolean;
|
|
293
308
|
startCompanion(): void;
|
|
294
309
|
stopCompanion(): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as s,c as i,e as u}from"./chunk-UN3Q4UGZ.js";import{a as t}from"./chunk-XUSV3KOD.js";var n=class extends Error{static{t(this,"EverframeNotMountedError")}name="EverframeNotMountedError";constructor(o){super(o??"EverframeProvider is not mounted; wrap your app root.")}},c="EVERFRAME_REPORTER_DISABLED",a=class extends Error{static{t(this,"EverframeReporterDisabledError")}name="EverframeReporterDisabledError";constructor(o){super(o??"The Everframe reporter is disabled in this session (reporter.disabled).")}};var r=null,p={};function f(){return p}t(f,"__getContextEpoch");function g(){try{return r?.getPromiseRejectionStatus()??u("not-mounted","no-mount")}catch{return u("install-failed","hook-install")}}t(g,"getPromiseRejectionStatus");function R(e){r=e,p={}}t(R,"__setCurrentContext");function S(){return r}t(S,"__getCurrentContext");function y(){let e=r;return e?e.open():Promise.reject(new n("EverframeProvider not mounted; wrap your root with <EverframeProvider>."))}t(y,"open");function C(e){r?.setExtra(e)}t(C,"setExtra");function b(e){r?.addBreadcrumb(e)}t(b,"addBreadcrumb");function P(e,o){o!==void 0?r?.recordScreen(e,o):r?.recordScreen(e)}t(P,"recordScreen");function _(e){r?.setUser(e)}t(_,"setUser");function j(e,o){r?.captureException(e,o)}t(j,"captureException");function w(){try{return r?.getErrorCaptureStatus()??i("not-mounted","no-mount")}catch{return i("not-mounted","no-mount")}}t(w,"getErrorCaptureStatus");function D(){let e=r;try{let o=e?.getReportDeliveryStatus();return e===r&&o?o:s("not-mounted","no-mount")}catch{return s("unavailable","native-call-failed")}}t(D,"getReportDeliveryStatus");export{n as a,c as b,a as c,f as d,g as e,R as f,S as g,y as h,C as i,b as j,P as k,_ as l,j as m,w as n,D as o};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as o}from"./chunk-XUSV3KOD.js";var h=["native-handled","bridge-handled","bridge-automatic","jvm-uncaught"],K=["persisted","disabled","admission-suppressed","reentrant","invalid-input","ownership-lost","storage-unavailable","failed"],S=["enqueue-committed","enqueue-failed","capacity-evicted","removed-after-acceptance","removed-after-terminal","removal-failed","read-failed"],T=["capacity","key-unavailable","corrupt","io","revoked","invalid-entry","busy","unknown","unsupported-format"],R=["live-submit","outbox-drain"],A=["server-accepted","retryable-http","terminal-http","network-failure","authorization-cancelled","cancelled","failed"],V=JSON.parse,H=Object.keys,y=Object.hasOwn,Q=Array.isArray,U=Number.isInteger,I=Reflect.apply,z=RegExp.prototype.test,_=/[^\x00-\x7f]/,O=2147483647;function d(e,t){let r={};for(let n=0;n<e.length;n++){let a=e[n];r[a]=t(a)}return r}o(d,"mapKeys");function J(e,t){return{schemaVersion:1,status:e,reason:t,scope:"native-runtime-observations",coverage:"best-effort",revision:0,capture:{enabled:!1,paths:d(h,()=>({supported:!1,settledAttempts:0,outcomes:d(K,()=>0)}))},queue:{scope:"sdk-report-outbox",observation:"not-observed",quality:"unknown",capacityPolicy:"unknown",terminalHttpPolicy:"unknown",operations:d(S,()=>0)},transport:d(R,()=>({settledAttempts:0,outcomes:d(A,()=>0)}))}}o(J,"emptyReportDeliveryStatus");function c(e,t,r=[]){if(!e||typeof e!="object"||Q(e))throw new Error;let n=H(e);for(let a=0;a<t.length;a++)if(!y(e,t[a]))throw new Error;for(let a=0;a<n.length;a++){let u=n[a];if(!g(t,u)&&!g(r,u))throw new Error}return e}o(c,"record");function g(e,t){for(let r=0;r<e.length;r++)if(e[r]===t)return!0;return!1}o(g,"contains");function l(e,t){if(typeof e!="string"||!g(t,e))throw new Error;return e}o(l,"enumeration");function v(e){if(typeof e!="number"||!U(e)||e<0||e>O)throw new Error;return e}o(v,"counter");function P(e){if(typeof e!="boolean")throw new Error;return e}o(P,"boolean");function q(e,t){let r=c(e,t);return d(t,n=>v(r[n]))}o(q,"counters");function j(e,t){let r=q(e.outcomes,t),n=v(e.settledAttempts),a=0;for(let b=0;b<t.length;b++)a+=r[t[b]];if((a>O?O:a)!==n)throw new Error;let u=y(e,"lastOutcome")?l(e.lastOutcome,t):void 0;if(n>0!=(u!==void 0)||u!==void 0&&r[u]===0)throw new Error;return{settledAttempts:n,outcomes:r,...u===void 0?{}:{lastOutcome:u}}}o(j,"settled");function C(e,t,r){for(let n=0;n<t.length;n++)if(r(e[t[n]])!==0)return!0;return!1}o(C,"nonzero");function M(e){try{if(typeof e!="string"||e.length>16384||I(z,_,[e]))return null;let t=c(V(e),["schemaVersion","status","reason","scope","coverage","revision","capture","queue","transport"]);if(t.schemaVersion!==1||t.scope!=="native-runtime-observations"||t.coverage!=="best-effort")return null;let r=l(t.status,["active","not-started","disabled","unavailable"]),n=l(t.reason,["none","no-start","capture-disabled","snapshot-busy"]);if({active:"none","not-started":"no-start",disabled:"capture-disabled",unavailable:"snapshot-busy"}[r]!==n)return null;let a=c(t.capture,["enabled","paths"]),u=c(a.paths,h),b=d(h,p=>{let f=c(u[p],["supported","settledAttempts","outcomes"],["lastOutcome"]),m={supported:P(f.supported),...j(f,K)};if(!m.supported&&m.settledAttempts!==0)throw new Error;return m}),i=c(t.queue,["scope","observation","quality","capacityPolicy","terminalHttpPolicy","operations"],["pendingCount","lastFailure"]);if(i.scope!=="sdk-report-outbox")return null;let s={scope:"sdk-report-outbox",observation:l(i.observation,["not-observed","observed","failed"]),quality:l(i.quality,["complete","partial","unknown"]),capacityPolicy:l(i.capacityPolicy,["reject-new","evict-oldest"]),terminalHttpPolicy:l(i.terminalHttpPolicy,["retain","attempt-remove"]),operations:q(i.operations,S),...y(i,"pendingCount")?{pendingCount:v(i.pendingCount)}:{},...y(i,"lastFailure")?{lastFailure:l(i.lastFailure,T)}:{}};if(s.capacityPolicy==="reject-new"!=(s.terminalHttpPolicy==="retain")||(s.observation!=="observed"||s.quality==="unknown")&&s.pendingCount!==void 0||s.observation!=="observed"&&s.quality!=="unknown"||s.observation==="failed"!=(s.lastFailure!==void 0))return null;let D=c(t.transport,R),E=d(R,p=>{let f=c(D[p],["settledAttempts","outcomes"],["lastOutcome","lastHttpStatus"]),m=j(f,A);if(y(f,"lastHttpStatus")){let x=v(f.lastHttpStatus);if(x<100||x>599||!g(["server-accepted","retryable-http","terminal-http"],m.lastOutcome??""))throw new Error;m.lastHttpStatus=x}return m}),w={schemaVersion:1,status:r,reason:n,scope:"native-runtime-observations",coverage:"best-effort",revision:v(t.revision),capture:{enabled:P(a.enabled),paths:b},queue:s,transport:E};return r!=="active"&&(w.revision!==0||w.capture.enabled||s.observation!=="not-observed"||C(b,h,p=>p.settledAttempts)||C(E,R,p=>p.settledAttempts)||C(s.operations,S,p=>p))?null:w}catch{return null}}o(M,"parseReportDeliveryStatus");function k(){return{attempted:0,accepted:0,duplicateSuppressed:0,allowanceSuppressed:0,bridgeUnavailable:0,nativeRefused:0,captureFailed:0,reentrantSuppressed:0,inactiveAborted:0}}o(k,"emptyCounters");function N(e,t){return{status:e,reason:t,scope:"mounted-js-admission",limits:{handled:10,automatic:10},counters:{handled:k(),errorUtils:k(),rejection:k()}}}o(N,"emptyErrorCaptureStatus");function F(e,t){e[t]=Math.min(2147483647,e[t]+1)}o(F,"incrementErrorCaptureCounter");function B(){let e=N("active","none").counters;return{begin(t){F(e[t],"attempted");let r=!1;return n=>{r||(r=!0,F(e[t],n))}},snapshot(){return{handled:{...e.handled},errorUtils:{...e.errorUtils},rejection:{...e.rejection}}}}}o(B,"createErrorCaptureLedger");function Y(e,t){return{status:e,reason:t,previousCallbacksPresent:!1,counters:{pending:0,accepted:0,cancelled:0,capacityDropped:0,sizeDropped:0,expired:0,duplicateSuppressed:0,allowanceSuppressed:0,nativeRefused:0,captureFailed:0,discarded:0}}}o(Y,"emptyPromiseRejectionStatus");export{J as a,M as b,N as c,B as d,Y as e};
|