@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.
@@ -1,5 +1,5 @@
1
- import { E as ErrorCaptureStatus, P as PromiseRejectionStatus, R as ReportDeliveryStatus } from './promise-rejection-types-C-lxCdDl.js';
2
- export { a as ErrorCaptureCounters, b as ErrorCaptureOutcome, c as ErrorCapturePath, d as PromiseRejectionCounters, e as RejectionReason, f as ReportCaptureOutcome, g as ReportCapturePath, h as ReportCapturePathStatus, i as ReportDeliveryReason, j as ReportQueueOperation, k as ReportQueueStatus, l as ReportStorageFailure, m as ReportTransportOrigin, n as ReportTransportOutcome, o as ReportTransportStatus } from './promise-rejection-types-C-lxCdDl.js';
1
+ import { E as ErrorCaptureStatus, P as PromiseRejectionStatus, R as ReportDeliveryStatus } from './promise-rejection-types-CiyBTz7v.js';
2
+ export { a as ErrorCaptureCounters, b as ErrorCaptureOutcome, c as ErrorCapturePath, d as PromiseRejectionCounters, e as RejectionReason, f as ReportCaptureOutcome, g as ReportCapturePath, h as ReportCapturePathStatus, i as ReportDeliveryReason, j as ReportQueueOperation, k as ReportQueueStatus, l as ReportStorageFailure, m as ReportTransportOrigin, n as ReportTransportOutcome, o as ReportTransportStatus } from './promise-rejection-types-CiyBTz7v.js';
3
3
  export * from '@everframe/react';
4
4
 
5
5
  /** Hermes observation is separate from the browser SDK's own rejection handling. */
package/dist/index.web.js CHANGED
@@ -1 +1 @@
1
- import{a as r,c as e,e as o}from"./chunk-ENPQ66EV.js";import{a as t}from"./chunk-XUSV3KOD.js";export*from"@everframe/react";function a(){return o("unsupported","platform")}t(a,"getPromiseRejectionStatus");function s(){return e("unsupported","platform")}t(s,"getErrorCaptureStatus");function n(){return r("unsupported","platform")}t(n,"getReportDeliveryStatus");export{s as getErrorCaptureStatus,a as getPromiseRejectionStatus,n as getReportDeliveryStatus};
1
+ import{a as r,c as e,e as o}from"./chunk-UN3Q4UGZ.js";import{a as t}from"./chunk-XUSV3KOD.js";export*from"@everframe/react";function a(){return o("unsupported","platform")}t(a,"getPromiseRejectionStatus");function s(){return e("unsupported","platform")}t(s,"getErrorCaptureStatus");function n(){return r("unsupported","platform")}t(n,"getReportDeliveryStatus");export{s as getErrorCaptureStatus,a as getPromiseRejectionStatus,n as getReportDeliveryStatus};
@@ -1 +1 @@
1
- import{h as c}from"../chunk-POE5CSO5.js";import"../chunk-ENPQ66EV.js";import{a as i}from"../chunk-XUSV3KOD.js";var g={log:"info",info:"info",warn:"warn",error:"error",debug:"debug"},a=2048;function p(t){let l=[...new Set(t?.levels??["log","info","warn","error"])];return{name:"console",setup(){let n=!1,e=[];for(let o of l){let r=console[o];if(typeof r!="function")continue;e.push({level:o,fn:r});let u=r.bind(console);console[o]=(...f)=>{if(u(...f),!n){n=!0;try{let s=v(f);s&&!s.startsWith("[everframe]")&&c({message:s,kind:"console",level:g[o]})}catch{}finally{n=!1}}}}return()=>{for(let{level:o,fn:r}of e)console[o]=r}}}}i(p,"consoleIntegration");function v(t){let n=t.map(e=>{if(typeof e=="string")return e;if(e instanceof Error)return e.message;try{return JSON.stringify(e)??String(e)}catch{return String(e)}}).join(" ");return n.length>a?n.slice(0,a):n}i(v,"serializeArgs");export{p as consoleIntegration};
1
+ import{j as c}from"../chunk-MIWWEGLO.js";import"../chunk-UN3Q4UGZ.js";import{a as i}from"../chunk-XUSV3KOD.js";var g={log:"info",info:"info",warn:"warn",error:"error",debug:"debug"},a=2048;function p(t){let l=[...new Set(t?.levels??["log","info","warn","error"])];return{name:"console",setup(){let n=!1,e=[];for(let o of l){let r=console[o];if(typeof r!="function")continue;e.push({level:o,fn:r});let u=r.bind(console);console[o]=(...f)=>{if(u(...f),!n){n=!0;try{let s=v(f);s&&!s.startsWith("[everframe]")&&c({message:s,kind:"console",level:g[o]})}catch{}finally{n=!1}}}}return()=>{for(let{level:o,fn:r}of e)console[o]=r}}}}i(p,"consoleIntegration");function v(t){let n=t.map(e=>{if(typeof e=="string")return e;if(e instanceof Error)return e.message;try{return JSON.stringify(e)??String(e)}catch{return String(e)}}).join(" ");return n.length>a?n.slice(0,a):n}i(v,"serializeArgs");export{p as consoleIntegration};
@@ -1 +1 @@
1
- import{i}from"../chunk-POE5CSO5.js";import"../chunk-ENPQ66EV.js";import{a as n}from"../chunk-XUSV3KOD.js";function o(t){let e=n(()=>{try{let a=t.navigationRef.getCurrentRoute?.()?.name;a&&i(a)}catch{}},"emit");return{name:"react-navigation",onReady:e,onStateChange:e,setup(){try{t.navigationRef.isReady()&&e()}catch{}try{return t.navigationRef.addListener("state",e)}catch{return}}}}n(o,"reactNavigationIntegration");export{o as reactNavigationIntegration};
1
+ import{k as i}from"../chunk-MIWWEGLO.js";import"../chunk-UN3Q4UGZ.js";import{a as n}from"../chunk-XUSV3KOD.js";function o(t){let e=n(()=>{try{let a=t.navigationRef.getCurrentRoute?.()?.name;a&&i(a)}catch{}},"emit");return{name:"react-navigation",onReady:e,onStateChange:e,setup(){try{t.navigationRef.isReady()&&e()}catch{}try{return t.navigationRef.addListener("state",e)}catch{return}}}}n(o,"reactNavigationIntegration");export{o as reactNavigationIntegration};
@@ -1 +1 @@
1
- import{b as n,e as a}from"../chunk-POE5CSO5.js";import"../chunk-ENPQ66EV.js";import{a as c}from"../chunk-XUSV3KOD.js";var d=Reflect.apply,u;function l(t,i){let r=a();if(!r){s();return}let p=n(),o;try{if(i!=null){let e=Object.getOwnPropertyDescriptor(i,"componentStack");e&&"value"in e&&typeof e.value=="string"&&(o=e.value)}}catch{}try{let e=r.captureException;if(a()!==r||n()!==p)return;d(e,r,[t,{context:"react.error-boundary",...o!==void 0?{metadata:{componentStack:o}}:{}}])}catch{}}c(l,"captureReactError");function s(){try{let t=n();if(u===t||typeof __DEV__>"u"||!__DEV__)return;u=t,console.warn("[everframe] captureReactError: no EverframeProvider is mounted, so this error was not captured. Render the error boundary inside <EverframeProvider>; errors caught during its first commit are not captured.")}catch{}}c(s,"warnNotMounted");export{l as captureReactError};
1
+ import{d as n,g as a}from"../chunk-MIWWEGLO.js";import"../chunk-UN3Q4UGZ.js";import{a as c}from"../chunk-XUSV3KOD.js";var d=Reflect.apply,u;function l(t,i){let r=a();if(!r){s();return}let p=n(),o;try{if(i!=null){let e=Object.getOwnPropertyDescriptor(i,"componentStack");e&&"value"in e&&typeof e.value=="string"&&(o=e.value)}}catch{}try{let e=r.captureException;if(a()!==r||n()!==p)return;d(e,r,[t,{context:"react.error-boundary",...o!==void 0?{metadata:{componentStack:o}}:{}}])}catch{}}c(l,"captureReactError");function s(){try{let t=n();if(u===t||typeof __DEV__>"u"||!__DEV__)return;u=t,console.warn("[everframe] captureReactError: no EverframeProvider is mounted, so this error was not captured. Render the error boundary inside <EverframeProvider>; errors caught during its first commit are not captured.")}catch{}}c(s,"warnNotMounted");export{l as captureReactError};
@@ -4,13 +4,13 @@ declare const queueOperations: readonly ["enqueue-committed", "enqueue-failed",
4
4
  declare const storageFailures: readonly ["capacity", "key-unavailable", "corrupt", "io", "revoked", "invalid-entry", "busy", "unknown", "unsupported-format"];
5
5
  declare const transportOrigins: readonly ["live-submit", "outbox-drain"];
6
6
  declare const transportOutcomes: readonly ["server-accepted", "retryable-http", "terminal-http", "network-failure", "authorization-cancelled", "cancelled", "failed"];
7
- type ReportCapturePath = typeof capturePaths[number];
8
- type ReportCaptureOutcome = typeof captureOutcomes[number];
9
- type ReportQueueOperation = typeof queueOperations[number];
10
- type ReportStorageFailure = typeof storageFailures[number];
11
- type ReportTransportOrigin = typeof transportOrigins[number];
12
- type ReportTransportOutcome = typeof transportOutcomes[number];
13
- type ReportDeliveryReason = 'none' | 'no-start' | 'capture-disabled' | 'no-mount' | 'platform' | 'native-method-missing' | 'native-call-failed' | 'invalid-native-snapshot' | 'snapshot-busy';
7
+ type ReportCapturePath = (typeof capturePaths)[number];
8
+ type ReportCaptureOutcome = (typeof captureOutcomes)[number];
9
+ type ReportQueueOperation = (typeof queueOperations)[number];
10
+ type ReportStorageFailure = (typeof storageFailures)[number];
11
+ type ReportTransportOrigin = (typeof transportOrigins)[number];
12
+ type ReportTransportOutcome = (typeof transportOutcomes)[number];
13
+ type ReportDeliveryReason = 'none' | 'no-start' | 'capture-disabled' | 'no-mount' | 'platform' | 'native-call-failed' | 'invalid-native-snapshot' | 'snapshot-busy';
14
14
  interface ReportCapturePathStatus {
15
15
  supported: boolean;
16
16
  settledAttempts: number;
@@ -26,7 +26,6 @@ interface ReportQueueStatus {
26
26
  terminalHttpPolicy: 'retain' | 'attempt-remove' | 'unknown';
27
27
  operations: Record<ReportQueueOperation, number>;
28
28
  lastFailure?: ReportStorageFailure;
29
- migration: 'not-observed' | 'clear' | 'blocked' | 'unknown';
30
29
  }
31
30
  interface ReportTransportStatus {
32
31
  settledAttempts: number;
@@ -61,7 +60,6 @@ interface ErrorCaptureCounters {
61
60
  captureFailed: number;
62
61
  reentrantSuppressed: number;
63
62
  inactiveAborted: number;
64
- legacyAttempted: number;
65
63
  }
66
64
  type ErrorCaptureOutcome = Exclude<keyof ErrorCaptureCounters, 'attempted'>;
67
65
  interface ErrorCaptureStatus {
@@ -7,7 +7,7 @@
7
7
  // new D-decision):
8
8
  //
9
9
  // The RN bridge surface is the Phase 06 5-method TurboModule (configure,
10
- // openReporter, registerSensitiveRect, setExtra, attachReactTree). The native
10
+ // openReporter, registerSensitiveRect, setExtra). The native
11
11
  // EFReporterPresenter owns the entire report UX (capture + annotate + redact +
12
12
  // submit) on iOS/iPadOS. On tvOS the on-device modal reporter was removed —
13
13
  // tvOS hosts call the companion start API and route reporting through the
@@ -26,6 +26,7 @@
26
26
  // • The presenter resolver is installed exactly once per process (idempotent
27
27
  // installer + a local flag for defense-in-depth).
28
28
  import Foundation
29
+ import CoreFoundation
29
30
  import Combine
30
31
  import os.log
31
32
  import UIKit
@@ -66,7 +67,8 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
66
67
  releaseHealthEnabled: Bool,
67
68
  releaseHealthLoadedBuildId: NSString?,
68
69
  releaseHealthUserId: NSString?,
69
- releaseHealthIncludeDebugBuilds: Bool
70
+ releaseHealthIncludeDebugBuilds: Bool,
71
+ reporterDisabled: Bool = false
70
72
  ) throws {
71
73
  configureLock.lock()
72
74
  defer { configureLock.unlock() }
@@ -101,6 +103,9 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
101
103
  // badge) exist, mirroring `companionAttachPinUi` below.
102
104
  cfg.companionBadgeEnabled = companionBadgeEnabled
103
105
  cfg.shakeToReportEnabled = shakeToReportEnabled
106
+ // Polarity flips exactly once, here: JS sends the crash-only switch
107
+ // (`reporterDisabled`), native stores the permission.
108
+ cfg.reporterEnabled = !reporterDisabled
104
109
  cfg.companionBadgePosition = companionBadgePosition as String?
105
110
  // Inline reporter theme (reporter branding spec 2026-08-25, RN
106
111
  // slice). The .mm collapses the 8 flat ConfigOpts `theme*` wire
@@ -275,7 +280,7 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
275
280
  /// every `configure()` is cheap, harmless churn, same as Android already
276
281
  /// accepts on every module construction.
277
282
  ///
278
- /// Returns the fresh generation so `EverframeModule.mm`'s `-configure:`
283
+ /// Returns the fresh generation so `EverframeModule.mm`'s `-configureSync:`
279
284
  /// can capture it (via `currentExtraResolveHookGeneration()` below) and
280
285
  /// hand it back to `resetExtraResolverForTeardown(_:)` at `-invalidate`
281
286
  /// time. `@discardableResult` because `configure()` itself has no use
@@ -292,7 +297,7 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
292
297
 
293
298
  /// ObjC-visible read of whichever generation is live RIGHT NOW — i.e.
294
299
  /// whatever the most recent `installExtraResolveHook()` call minted.
295
- /// `EverframeModule.mm`'s `-configure:` calls this immediately after
300
+ /// `EverframeModule.mm`'s `-configureSync:` calls this immediately after
296
301
  /// `configureWithSdkKey:...:error:` (which, on success or on the
297
302
  /// already-running-unchanged skip path, calls `installExtraResolveHook()`
298
303
  /// internally) and stashes the result on its own instance ivar for
@@ -397,7 +402,7 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
397
402
  /// `RemoteVitalsBridge.reset()`'s own lifetime doc comment.
398
403
  ///
399
404
  /// IDENTITY GUARD (D1 fix — stale-teardown race). `generation` is the
400
- /// value `EverframeModule.mm`'s `-configure:` captured via
405
+ /// value `EverframeModule.mm`'s `-configureSync:` captured via
401
406
  /// `currentExtraResolveHookGeneration()` right after ITS OWN
402
407
  /// `configure()` call, stashed on that ObjC instance and handed back
403
408
  /// here at `-invalidate` time. RN dispatches `-invalidate` on a
@@ -559,12 +564,29 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
559
564
  dict = ["status": "cancelled"]
560
565
  }
561
566
  completion(dict, nil)
567
+ } catch EverframeReporterError.reporterDisabled {
568
+ completion(nil, NSError(
569
+ domain: "EverframeBridge", code: reporterDisabledErrorCode,
570
+ userInfo: [NSLocalizedDescriptionKey: EverframeReporterError.reporterDisabled.localizedDescription]
571
+ ))
562
572
  } catch {
563
573
  completion(nil, error as NSError)
564
574
  }
565
575
  }
566
576
  }
567
577
 
578
+ /// `openReporter`'s error for a crash-only session (`reporterDisabled: true`).
579
+ static let reporterDisabledErrorCode = 1101
580
+
581
+ /// The promise rejection code for an `openReporter` failure. A crash-only session gets
582
+ /// `EVERFRAME_REPORTER_DISABLED`, which the JS facade maps to
583
+ /// `EverframeReporterDisabledError`; anything else keeps `open_reporter_failed`.
584
+ @objc(openReporterRejectCodeFor:)
585
+ public static func openReporterRejectCode(for error: NSError) -> String {
586
+ error.domain == "EverframeBridge" && error.code == reporterDisabledErrorCode
587
+ ? "EVERFRAME_REPORTER_DISABLED" : "open_reporter_failed"
588
+ }
589
+
568
590
  // MARK: - registerSensitiveRect
569
591
 
570
592
  /// Mark a view as sensitive. Routed through MainActor.assumeIsolated so the
@@ -601,12 +623,31 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
601
623
  /// `everframe-react-native` (distinct from `CrashReporter.captureFacts`'s
602
624
  /// `"everframe-ios"` default) so envelopes are attributable to the RN SDK.
603
625
  @objc public static func reportCrash(_ crashJson: NSString) -> Bool {
604
- CrashReporter.captureFacts(json: crashJson as String, sdkName: "everframe-react-native")
626
+ let facts = crashJson as String
627
+ // Replays on errors (spec 2026-10-11): only an error the app survives asks for a replay of the seconds
628
+ // before it. A fatal JavaScript error ends the app, so it is a crash and never carries one.
629
+ guard appSurvivesError(facts) else {
630
+ return CrashReporter.captureFacts(json: facts, sdkName: "everframe-react-native", attachReplay: false)
631
+ }
632
+ return CrashReporter.captureFacts(json: facts, sdkName: "everframe-react-native", attachReplay: true)
605
633
  }
606
634
 
607
- /// Explicit handled capture acknowledges the core's completed encrypted enqueue.
635
+ /// Explicit handled capture acknowledges the core's completed encrypted enqueue. It is always an error the
636
+ /// app survives, so it asks for a replay; the core decides whether the session qualifies.
608
637
  @objc public static func captureHandledException(_ crashJson: NSString) -> Bool {
609
- CrashReporter.captureHandledFacts(json: crashJson as String, sdkName: "everframe-react-native")
638
+ CrashReporter.captureHandledFacts(json: crashJson as String, sdkName: "everframe-react-native", attachReplay: true)
639
+ }
640
+
641
+ /// True when the facts describe an error the app survives: `fatal` is absent or the JSON boolean `false`,
642
+ /// which is how `CrashReporter` classifies facts that are not marked fatal. Anything unreadable counts as
643
+ /// fatal here, so it can only lose a replay, never ask for one on a crash.
644
+ static func appSurvivesError(_ facts: String) -> Bool {
645
+ guard let data = facts.data(using: .utf8),
646
+ let object = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] else { return false }
647
+ guard let flag = object["fatal"] else { return true }
648
+ guard let boolean = flag as? NSNumber,
649
+ CFGetTypeID(boolean) == CFBooleanGetTypeID() else { return false }
650
+ return !boolean.boolValue
610
651
  }
611
652
 
612
653
  // MARK: - setExtra
@@ -628,7 +669,7 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
628
669
  /// `Everframe.shared.addBreadcrumb(message:kind:level:data:)` (Task 5) —
629
670
  /// NO validation/coercion happens here. That singleton already owns
630
671
  /// kind/level/data coercion AND gating (no-op pre-start / while killed),
631
- /// exactly like `setExtra`/`attachReactTree` above never re-check
672
+ /// exactly like `setExtra` above never re-checks
632
673
  /// `captureGate` themselves. `data` crosses as an `NSDictionary?` — the
633
674
  /// codegen-typed `UnsafeObject` boundary for freeform objects; a
634
675
  /// non-dictionary payload (shouldn't happen via codegen, but fail-soft
@@ -652,8 +693,8 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
652
693
 
653
694
  /// Forward a host-declared user from JS into `Everframe.shared.setUser`.
654
695
  /// Pure pass-through: the native singleton owns storage — NO
655
- /// validation/coercion happens here, same contract as `setExtra`/
656
- /// `attachReactTree` above. `nil` clears the active user (the bridge
696
+ /// validation/coercion happens here, same contract as `setExtra`
697
+ /// above. `nil` clears the active user (the bridge
657
698
  /// forbids `T | null`, so JS expresses "clear" by calling with no
658
699
  /// argument, which crosses as `nil` here). A missing key on the incoming
659
700
  /// dictionary must become `nil`, never an empty string — that's what
@@ -746,9 +787,7 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
746
787
  nonisolated(unsafe) private static var companionClient: RelayWSClient?
747
788
  /// The host's configured attach-PIN UI mode (spec 2026-08-19), parsed
748
789
  /// from `ConfigOpts.attachPinUi` at `configure()` time. Defaults to
749
- /// `.builtin` — the pre-existing-behaviour default for a host that never
750
- /// set the flag (including every host on an older RN SDK version whose
751
- /// `configure()` call carries no such field at all).
790
+ /// `.builtin` for a host that never sets the flag.
752
791
  nonisolated(unsafe) private static var companionAttachPinUi: AttachPinUi = .builtin
753
792
  // `CompanionCaptureBridge` subscribes to the report.request notification
754
793
  // posted by RelayWSClient and runs the capture+send dance. Without an
@@ -70,11 +70,9 @@ public final class EverframeEventEmitter: RCTEventEmitter {
70
70
  /// Payload: `{ code, requestedByName, ttlMs }` | NSNull.
71
71
  public static let attachChallengeEvent = "everframe.companion.attachChallenge"
72
72
 
73
- /// Fired when a `report.request` arrives from the paired phone. This
74
- /// used to give JS a window to walk the React fiber tree and call
75
- /// `attachReactTree`; both are gone (spec 2026-08-29). The event and
76
- /// the bridge's brief wait survive as the companion's JS-ready
77
- /// handshake (see CompanionCaptureBridge.awaitJsReactTreeAttach).
73
+ /// Fired when a `report.request` arrives from the paired phone. The event
74
+ /// and the bridge's brief wait are the companion's JS-ready handshake
75
+ /// (see CompanionCaptureBridge.awaitJsReactTreeAttach).
78
76
  /// Payload: String — the correlation_id of the inbound request.
79
77
  public static let reportRequestedEvent = "everframe.companion.reportRequested"
80
78
 
@@ -6,7 +6,7 @@
6
6
  // to the Swift glue layer (`EverframeBridge` in `EverframeBridge.swift`).
7
7
  //
8
8
  // Threading (RESEARCH §2.5):
9
- // • configure — JS thread (synchronous; <5ms per sdk-ios contract)
9
+ // • configureSync — JS thread (synchronous; <5ms per sdk-ios contract)
10
10
  // • captureNow — main queue (UIKit reads, capture, accessibility walk)
11
11
  // • registerSensitiveRect — main queue (RCTUIManager view lookup)
12
12
  // • submit — background OK (no UIKit reads); bridge runs it
@@ -75,11 +75,11 @@
75
75
  RemoteVitalsBridge *_vitals;
76
76
  // D1 fix (stale-teardown identity guard, spec 2026-09-17 setExtra-resolver).
77
77
  // Snapshot of `[EverframeBridge currentExtraResolveHookGeneration]` taken
78
- // right after THIS instance's most recent `-configure:` call — see
78
+ // right after THIS instance's most recent `-configureSync:` call — see
79
79
  // `EverframeBridge.resetExtraResolverForTeardown(_:)`'s doc comment for why
80
80
  // `-invalidate` must hand this back rather than tearing down unconditionally.
81
81
  // Zero-initialized by ObjC, matching `resetExtraResolverForTeardown(_:)`'s
82
- // "0 = never installed" no-op case for an instance whose `-configure:` was
82
+ // "0 = never installed" no-op case for an instance whose `-configureSync:` was
83
83
  // never called (or never reached the install site) before `-invalidate`.
84
84
  NSInteger _extraResolveHookGeneration;
85
85
  }
@@ -106,10 +106,12 @@ RCT_EXPORT_MODULE(Everframe)
106
106
  _bridge = bridge;
107
107
  }
108
108
 
109
- // MARK: - configure(opts)
109
+ // MARK: - configureSync(opts)
110
110
 
111
111
  // Signature MUST match the codegen-emitted protocol in EverframeSpec.h:
112
- // - (void)configure:(JS::NativeEverframe::ConfigOpts &)opts;
112
+ // - (NSNumber *)configureSync:(JS::NativeEverframe::ConfigOpts &)opts;
113
+ // Blocking rather than void: React Native dispatches void TurboModule methods
114
+ // asynchronously, and JS's first capture after mount must not overtake it.
113
115
  // Field accessors (.sdkKey(), .sdkVersion(), .captureScreenshot(),
114
116
  // .networkBodiesDisabled()) are inline methods on the JS::NativeEverframe::
115
117
  // ConfigOpts struct, generated from the TS spec in src/NativeEverframe.ts.
@@ -120,7 +122,7 @@ RCT_EXPORT_MODULE(Everframe)
120
122
  // into ConfigOpts by extractBridgeConfig() but is never read below, so it is
121
123
  // silently dropped on iOS today. Task 9 (spec 2026-08-12) only wires
122
124
  // `networkBodiesDisabled` through; see task-9-report.md.
123
- - (void)configure:(JS::NativeEverframe::ConfigOpts &)opts {
125
+ - (NSNumber *)configureSync:(JS::NativeEverframe::ConfigOpts &)opts {
124
126
  NSString *sdkKey = opts.sdkKey() ?: @"";
125
127
  BOOL networkBodiesDisabled = opts.networkBodiesDisabled().value_or(false);
126
128
  // `installIdentifierDisabled` (MAI meter spec 2026-08-27) — `std::optional
@@ -151,6 +153,9 @@ RCT_EXPORT_MODULE(Everframe)
151
153
  // above; `EverframeBridge` coerces nil/unrecognised to `.bottomRight`.
152
154
  BOOL companionBadgeEnabled = opts.companionBadgeEnabled().value_or(true);
153
155
  BOOL shakeToReportEnabled = opts.shakeToReportEnabled().value_or(true);
156
+ // Crash-only session switch (spec 2026-10-12) — `std::optional<bool>`, same shape as
157
+ // `networkBodiesDisabled()`. Absent means a normal report session.
158
+ BOOL reporterDisabled = opts.reporterDisabled().value_or(false);
154
159
  NSString *companionBadgePosition = opts.companionBadgePosition();
155
160
  // Inline reporter theme (reporter branding spec 2026-08-25, RN slice) —
156
161
  // 8 flat optional STRING fields on ConfigOpts (RN codegen cannot express
@@ -206,9 +211,10 @@ RCT_EXPORT_MODULE(Everframe)
206
211
  releaseHealthLoadedBuildId:opts.releaseHealthLoadedBuildId()
207
212
  releaseHealthUserId:opts.releaseHealthUserId()
208
213
  releaseHealthIncludeDebugBuilds:opts.releaseHealthIncludeDebugBuilds().value_or(false)
214
+ reporterDisabled:reporterDisabled
209
215
  error:&err];
210
216
  if (err != nil) {
211
- // Configure is void-typed at the spec; we can't reject a promise here.
217
+ // A refused configuration is logged; JS keeps its mount either way.
212
218
  RCTLogWarn(@"[everframe] configure failed: %@", err.localizedDescription);
213
219
  }
214
220
  // D1 fix (stale-teardown identity guard, spec 2026-09-17 setExtra-resolver).
@@ -224,13 +230,6 @@ RCT_EXPORT_MODULE(Everframe)
224
230
  // the effective options are unchanged and the SDK is already started, and when it does
225
231
  // restart, the JS `configureEpoch` bump re-runs every vitals hook effect — each one
226
232
  // detaching its old token and tracking a fresh one against the SDK that is now current.
227
- }
228
-
229
- // Additive synchronous startup entry. The legacy void selector above stays
230
- // ABI-compatible for already-compiled callers; current JS uses this boolean
231
- // method so its first synchronous capture cannot overtake configure dispatch.
232
- - (NSNumber *)configureSync:(JS::NativeEverframe::ConfigOpts &)opts {
233
- [self configure:opts];
234
233
  return @YES;
235
234
  }
236
235
 
@@ -242,7 +241,9 @@ RCT_EXPORT_MODULE(Everframe)
242
241
  reject:(RCTPromiseRejectBlock)reject {
243
242
  [EverframeBridge openReporterWithCompletion:^(NSDictionary *result, NSError *err) {
244
243
  if (err) {
245
- reject(@"open_reporter_failed", err.localizedDescription, err);
244
+ // `EVERFRAME_REPORTER_DISABLED` for a crash-only session (the JS facade maps it to
245
+ // `EverframeReporterDisabledError`); `open_reporter_failed` for every other failure.
246
+ reject([EverframeBridge openReporterRejectCodeFor:err], err.localizedDescription, err);
246
247
  } else {
247
248
  resolve(result ?: @{});
248
249
  }
@@ -403,7 +404,7 @@ RCT_EXPORT_MODULE(Everframe)
403
404
  // full 250ms ask-and-wait for an ack nobody will ever send.
404
405
  //
405
406
  // D1 fix: `_extraResolveHookGeneration` (captured at the end of
406
- // `-configure:`) travels with this instance so the Swift side can tell a
407
+ // `-configureSync:`) travels with this instance so the Swift side can tell a
407
408
  // STALE instance's `-invalidate` — delivered late, on this module's
408
409
  // method queue, after a reloaded bundle's fresh instance already
409
410
  // reconfigured — from the CURRENT one, and no-op the former. See
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@everframe/react-native",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "React Native SDK for Everframe — AI-ready in-app bug reporting, bridging the native iOS and Android reporter to your RN app. Supports phone, Apple TV, and Android TV.",
5
5
  "author": {
6
6
  "name": "Scriptx, UAB",
@@ -100,7 +100,7 @@
100
100
  "dependencies": {
101
101
  "bippy": "^0.5.39",
102
102
  "zod": "^4.3.0",
103
- "@everframe/react": "^1.2.0"
103
+ "@everframe/react": "^1.3.0"
104
104
  },
105
105
  "devDependencies": {
106
106
  "@arethetypeswrong/cli": "^0.18.0",
@@ -112,8 +112,8 @@
112
112
  "publint": "^0.3.0",
113
113
  "tsup": "^8.0.0",
114
114
  "vitest": "^4.0.0",
115
- "@everframe/protocol": "0.7.0",
116
- "@everframe/sdk-core": "0.6.0"
115
+ "@everframe/sdk-core": "0.6.0",
116
+ "@everframe/protocol": "0.7.0"
117
117
  },
118
118
  "codegenConfig": {
119
119
  "name": "EverframeSpec",
@@ -43,7 +43,7 @@
43
43
  // `setUser(user?: UnsafeObject)` generates
44
44
  // `- (void)setUser:(NSDictionary *)user;` (nullable, matching
45
45
  // `addBreadcrumb`'s `data?: UnsafeObject` above). This was harmless for
46
- // `configure(opts: ConfigOpts)` only because that parameter is REQUIRED,
46
+ // `configureSync(opts: ConfigOpts)` only because that parameter is REQUIRED,
47
47
  // never optional, so the alias-vs-UnsafeObject distinction never mattered
48
48
  // there. `EverframeUserSpec` stays exported as the JS-FACING type (runtime.ts /
49
49
  // contextSeam.ts / index.ts) — only the bridge parameter itself widens to
@@ -63,7 +63,7 @@
63
63
  // `AttachPinUi` enum (`enum AttachPinUi: String` on iOS, `enum class
64
64
  // AttachPinUi` on Android) and coerces anything unrecognised — including
65
65
  // an absent field, which decodes as `undefined` and never reaches native
66
- // at all — to `.builtin`/`BUILTIN`. This rides `configure()`, not a new
66
+ // at all — to `.builtin`/`BUILTIN`. This rides `configureSync()`, not a new
67
67
  // bridge method: the flag is a startup-time preference, not a per-call
68
68
  // argument, so it belongs beside `sdkKey`/`networkBodiesDisabled` on
69
69
  // `ConfigOpts` — see `companion.ts`'s `everframe.companion.attachChallenge`
@@ -85,9 +85,9 @@
85
85
  // the companion `reportRequested`/`signalCompanionReportRequestReady`
86
86
  // handshake below exactly), bounded and fail-open; see both methods' own
87
87
  // doc comments.
88
- import type { TurboModule } from "react-native";
89
- import { TurboModuleRegistry } from "react-native";
90
- import type { UnsafeObject } from "./codegen-types";
88
+ import type { TurboModule } from 'react-native';
89
+ import { TurboModuleRegistry } from 'react-native';
90
+ import type { UnsafeObject } from './codegen-types';
91
91
 
92
92
  /**
93
93
  * Geometry rect crossing the bridge. Flat-object only; codegen cannot express
@@ -171,6 +171,13 @@ export type ConfigOpts = {
171
171
  companionBadgeEnabled?: boolean;
172
172
  /** Local veto for native mobile shake-to-report. Absent defaults to enabled. */
173
173
  shakeToReportEnabled?: boolean;
174
+ /**
175
+ * Crash-only session switch. `true` turns off every on-device way to open the reporter, keeps
176
+ * network bodies off and samples replay with the server's crash-only rate until a companion
177
+ * bond upgrades the session. Flattened by `extractBridgeConfig` from `reporter: { disabled }`.
178
+ * Absent means a normal report session.
179
+ */
180
+ reporterDisabled?: boolean;
174
181
  /**
175
182
  * Companion on-screen name-badge corner (naming spec 2026-08-24,
176
183
  * controller ruling / Task 6b). Flat optional STRING, not a literal
@@ -254,11 +261,10 @@ export type EverframeUserSpec = {
254
261
  };
255
262
 
256
263
  export interface Spec extends TurboModule {
257
- configure(opts: ConfigOpts): void;
258
264
  /**
259
- * Synchronous startup entry used by the current JS runtime. The legacy
260
- * void configure method remains for already-compiled native callers, but
261
- * React Native dispatches void TurboModule methods asynchronously.
265
+ * Synchronous startup entry. It is blocking rather than void because React
266
+ * Native dispatches void TurboModule methods asynchronously, and the first
267
+ * capture after mount must not overtake configuration.
262
268
  */
263
269
  configureSync(opts: ConfigOpts): boolean;
264
270
  /**
@@ -324,12 +330,7 @@ export interface Spec extends TurboModule {
324
330
  * (no-op pre-start / while killed). `data` is typed `UnsafeObject` — the
325
331
  * documented codegen escape hatch for freeform objects (see file header).
326
332
  */
327
- addBreadcrumb(
328
- message: string,
329
- kind?: string,
330
- level?: string,
331
- data?: UnsafeObject,
332
- ): void;
333
+ addBreadcrumb(message: string, kind?: string, level?: string, data?: UnsafeObject): void;
333
334
 
334
335
  /**
335
336
  * Navigation screen marker (spec 2026-07-14 — the D-decision for this
@@ -353,6 +354,12 @@ export interface Spec extends TurboModule {
353
354
  * fatal, occurredAt }. Native builds/redacts/persists the envelope with
354
355
  * state it already owns (crumb ring, device metadata, outbox).
355
356
  * Returns true when the report was persisted.
357
+ *
358
+ * A non-fatal ErrorUtils error or unhandled promise rejection may carry a
359
+ * session replay of the seconds before it. A fatal error never carries one.
360
+ * Native decides replay eligibility; JavaScript sends no extra fields.
361
+ * Send `fatal` as a JSON boolean: only an absent flag or boolean false can
362
+ * request a replay on iOS.
356
363
  */
357
364
  reportCrash(crashJson: string): boolean;
358
365
 
@@ -369,12 +376,7 @@ export interface Spec extends TurboModule {
369
376
  // crosses here. Unknown/duplicate/detached tokens are silent no-ops.
370
377
 
371
378
  /** Register a remote player. A token already live is ignored. */
372
- trackPlayer(
373
- token: string,
374
- library: string,
375
- name?: string,
376
- libraryVersion?: string,
377
- ): void;
379
+ trackPlayer(token: string, library: string, name?: string, libraryVersion?: string): void;
378
380
  /** `handle.detach()` + drop the token. Native emits `player_detach`. */
379
381
  detachPlayer(token: string): void;
380
382
  /**
@@ -384,12 +386,7 @@ export interface Spec extends TurboModule {
384
386
  * enum); the native context's `emit` refuses anything outside
385
387
  * `VitalsPlayerEventType`. `data` is the `UnsafeObject` escape hatch.
386
388
  */
387
- recordPlayerEvent(
388
- token: string,
389
- type: string,
390
- t: number,
391
- data?: UnsafeObject,
392
- ): void;
389
+ recordPlayerEvent(token: string, type: string, t: number, data?: UnsafeObject): void;
393
390
  /**
394
391
  * Refresh the native snapshot cache: `{ bufferAheadMs, bandwidthEstimate?,
395
392
  * bitrate?, width?, height?, droppedFrames? }`. `droppedFrames` is
@@ -403,7 +400,10 @@ export interface Spec extends TurboModule {
403
400
  * `token` resolves to a live player, else session-scoped.
404
401
  */
405
402
  trackVitals(name: string, dataJson?: string, token?: string): void;
406
- /** Explicit handled capture; native enforces handled=true, fatal=false. */
403
+ /**
404
+ * Explicit handled capture; native enforces handled=true, fatal=false. Like a
405
+ * non-fatal `reportCrash`, it may carry a session replay.
406
+ */
407
407
  captureHandledException(crashJson: string): boolean;
408
408
 
409
409
  // ---------------- Phone-companion (Plan 06.2-11) ----------------
@@ -462,4 +462,4 @@ export interface Spec extends TurboModule {
462
462
  setUser(user?: UnsafeObject): void;
463
463
  }
464
464
 
465
- export default TurboModuleRegistry.getEnforcing<Spec>("Everframe");
465
+ export default TurboModuleRegistry.getEnforcing<Spec>('Everframe');
@@ -8,4 +8,3 @@
8
8
  * whose declaration is disabled by default in React Native 0.87.
9
9
  */
10
10
  export type UnsafeObject = object;
11
-
@@ -1 +0,0 @@
1
- import{a as o}from"./chunk-XUSV3KOD.js";var h=["native-handled","bridge-handled","bridge-automatic","jvm-uncaught"],A=["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"],K=["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]/,k=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(A,()=>0)}))},queue:{scope:"sdk-report-outbox",observation:"not-observed",quality:"unknown",capacityPolicy:"unknown",terminalHttpPolicy:"unknown",operations:d(S,()=>0),migration:"not-observed"},transport:d(R,()=>({settledAttempts:0,outcomes:d(K,()=>0)}))}}o(J,"emptyReportDeliveryStatus");function l(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 i=n[a];if(!g(t,i)&&!g(r,i))throw new Error}return e}o(l,"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 c(e,t){if(typeof e!="string"||!g(t,e))throw new Error;return e}o(c,"enumeration");function v(e){if(typeof e!="number"||!U(e)||e<0||e>k)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=l(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>k?k:a)!==n)throw new Error;let i=y(e,"lastOutcome")?c(e.lastOutcome,t):void 0;if(n>0!=(i!==void 0)||i!==void 0&&r[i]===0)throw new Error;return{settledAttempts:n,outcomes:r,...i===void 0?{}:{lastOutcome:i}}}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=l(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=c(t.status,["active","not-started","disabled","unavailable"]),n=c(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=l(t.capture,["enabled","paths"]),i=l(a.paths,h),b=d(h,p=>{let f=l(i[p],["supported","settledAttempts","outcomes"],["lastOutcome"]),m={supported:P(f.supported),...j(f,A)};if(!m.supported&&m.settledAttempts!==0)throw new Error;return m}),u=l(t.queue,["scope","observation","quality","capacityPolicy","terminalHttpPolicy","operations","migration"],["pendingCount","lastFailure"]);if(u.scope!=="sdk-report-outbox")return null;let s={scope:"sdk-report-outbox",observation:c(u.observation,["not-observed","observed","failed"]),quality:c(u.quality,["complete","partial","unknown"]),capacityPolicy:c(u.capacityPolicy,["reject-new","evict-oldest"]),terminalHttpPolicy:c(u.terminalHttpPolicy,["retain","attempt-remove"]),operations:q(u.operations,S),migration:c(u.migration,["not-observed","clear","blocked","unknown"]),...y(u,"pendingCount")?{pendingCount:v(u.pendingCount)}:{},...y(u,"lastFailure")?{lastFailure:c(u.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=l(t.transport,R),E=d(R,p=>{let f=l(D[p],["settledAttempts","outcomes"],["lastOutcome","lastHttpStatus"]),m=j(f,K);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 O(){return{attempted:0,accepted:0,duplicateSuppressed:0,allowanceSuppressed:0,bridgeUnavailable:0,nativeRefused:0,captureFailed:0,reentrantSuppressed:0,inactiveAborted:0,legacyAttempted:0}}o(O,"emptyCounters");function N(e,t){return{status:e,reason:t,scope:"mounted-js-admission",limits:{handled:10,automatic:10},counters:{handled:O(),errorUtils:O(),rejection:O()}}}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};
@@ -1 +0,0 @@
1
- import{a as i,c as s,e as u}from"./chunk-ENPQ66EV.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.")}};var r=null,a={};function x(){return a}t(x,"__getContextEpoch");function f(){try{return r?.getPromiseRejectionStatus?.()??u("not-mounted","no-mount")}catch{return u("install-failed","hook-install")}}t(f,"getPromiseRejectionStatus");function E(e){r=e,a={}}t(E,"__setCurrentContext");function g(){return r}t(g,"__getCurrentContext");function R(){let e=r;return e?e.open():Promise.reject(new n("EverframeProvider not mounted; wrap your root with <EverframeProvider>."))}t(R,"open");function S(e){r?.setExtra(e)}t(S,"setExtra");function y(e){r?.addBreadcrumb(e)}t(y,"addBreadcrumb");function C(e,o){o!==void 0?r?.recordScreen(e,o):r?.recordScreen(e)}t(C,"recordScreen");function b(e){r?.setUser(e)}t(b,"setUser");function j(e,o){r?.captureException(e,o)}t(j,"captureException");function w(){try{return r?.getErrorCaptureStatus?.()??s("not-mounted","no-mount")}catch{return s("not-mounted","no-mount")}}t(w,"getErrorCaptureStatus");function P(){let e=r;try{let o=e?.getReportDeliveryStatus?.();return e===r&&o?o:i("not-mounted","no-mount")}catch{return i("unavailable","native-call-failed")}}t(P,"getReportDeliveryStatus");export{n as a,x as b,f as c,E as d,g as e,R as f,S as g,y as h,C as i,b as j,j as k,w as l,P as m};