@everframe/react-native 1.1.0 → 1.2.1
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 +5 -1
- package/README.md +119 -40
- package/android/build.gradle.kts +7 -6
- package/android/src/main/java/dev/everframe/rn/EverframeModule.kt +86 -39
- package/android/src/main/java/dev/everframe/rn/ReporterBridge.kt +4 -13
- package/dist/NativeEverframe.d.ts +44 -6
- package/dist/browser-4GHZL7XI.js +1 -0
- package/dist/chunk-MIWWEGLO.js +1 -0
- package/dist/chunk-UN3Q4UGZ.js +1 -0
- package/dist/index.d.ts +353 -23
- package/dist/index.js +31 -25
- 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-Bf5iLv8n.d.ts} +1 -3
- package/ios/Sources/EverframeBridge.swift +105 -69
- package/ios/Sources/EverframeEventEmitter.swift +3 -5
- package/ios/Sources/EverframeModule.mm +34 -20
- package/package.json +4 -4
- package/src/NativeEverframe.ts +47 -9
- package/dist/browser-ZQAY6B4M.js +0 -1
- package/dist/chunk-ENPQ66EV.js +0 -1
- package/dist/chunk-POE5CSO5.js +0 -1
package/dist/index.web.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { E as ErrorCaptureStatus, P as PromiseRejectionStatus, R as ReportDeliveryStatus } from './promise-rejection-types-
|
|
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-
|
|
1
|
+
import { E as ErrorCaptureStatus, P as PromiseRejectionStatus, R as ReportDeliveryStatus } from './promise-rejection-types-Bf5iLv8n.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-Bf5iLv8n.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-
|
|
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{
|
|
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-
|
|
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{
|
|
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};
|
package/dist/{promise-rejection-types-C-lxCdDl.d.ts → promise-rejection-types-Bf5iLv8n.d.ts}
RENAMED
|
@@ -10,7 +10,7 @@ type ReportQueueOperation = typeof queueOperations[number];
|
|
|
10
10
|
type ReportStorageFailure = typeof storageFailures[number];
|
|
11
11
|
type ReportTransportOrigin = typeof transportOrigins[number];
|
|
12
12
|
type ReportTransportOutcome = typeof transportOutcomes[number];
|
|
13
|
-
type ReportDeliveryReason = 'none' | 'no-start' | 'capture-disabled' | 'no-mount' | 'platform' | 'native-
|
|
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
|
|
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
|
|
@@ -44,11 +45,15 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
|
|
|
44
45
|
|
|
45
46
|
// MARK: - configure
|
|
46
47
|
|
|
48
|
+
// Serialize the bridge's start/idempotence decision with synchronous health reservation.
|
|
49
|
+
private static let configureLock = NSRecursiveLock()
|
|
50
|
+
|
|
47
51
|
@objc public static func configure(
|
|
48
|
-
|
|
52
|
+
sdkKey: NSString,
|
|
49
53
|
endpoint: NSString,
|
|
50
54
|
networkBodiesDisabled: Bool,
|
|
51
55
|
installIdentifierDisabled: Bool,
|
|
56
|
+
crashReportingDisabled: Bool,
|
|
52
57
|
attachPinUi: NSString?,
|
|
53
58
|
companionDeviceId: NSString?,
|
|
54
59
|
companionBadgeEnabled: Bool,
|
|
@@ -57,21 +62,32 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
|
|
|
57
62
|
theme: NSDictionary?,
|
|
58
63
|
vitalsEnabled: NSNumber?,
|
|
59
64
|
vitalsSampleRate: NSNumber?,
|
|
60
|
-
vitalsCaptureSourceQuery: NSNumber
|
|
65
|
+
vitalsCaptureSourceQuery: NSNumber?,
|
|
66
|
+
debugBuild: NSNumber?,
|
|
67
|
+
releaseHealthEnabled: Bool,
|
|
68
|
+
releaseHealthLoadedBuildId: NSString?,
|
|
69
|
+
releaseHealthUserId: NSString?,
|
|
70
|
+
releaseHealthIncludeDebugBuilds: Bool,
|
|
71
|
+
reporterDisabled: Bool = false
|
|
61
72
|
) throws {
|
|
73
|
+
configureLock.lock()
|
|
74
|
+
defer { configureLock.unlock() }
|
|
62
75
|
// `endpoint` arg is retained for ObjC ABI compatibility with the
|
|
63
76
|
// codegen .mm — value is ignored. The SDK bakes the ingest URL at
|
|
64
77
|
// compile time (sdk-ios IngestEndpoint).
|
|
65
78
|
_ = endpoint
|
|
66
|
-
os_log("[everframe] configure
|
|
79
|
+
os_log("[everframe] configure sdkKey=%{public}@", log: log, type: .info, sdkKey as String)
|
|
67
80
|
// Polarity flips exactly once, here: JS sends the veto
|
|
68
81
|
// (`networkBodiesDisabled`), native stores the permission.
|
|
69
|
-
var cfg = EverframeConfig(
|
|
82
|
+
var cfg = EverframeConfig(sdkKey: sdkKey as String)
|
|
70
83
|
cfg.capture.networkBodies = !networkBodiesDisabled
|
|
71
84
|
// Polarity flips exactly once, here: JS sends the veto
|
|
72
85
|
// (`installIdentifierDisabled`), native stores the permission —
|
|
73
86
|
// identical to `networkBodies` above.
|
|
74
87
|
cfg.installIdentifierEnabled = !installIdentifierDisabled
|
|
88
|
+
// Polarity flips exactly once, here: JS sends the veto
|
|
89
|
+
// (`crashReportingDisabled`), native stores the permission.
|
|
90
|
+
cfg.capture.crash = !crashReportingDisabled
|
|
75
91
|
// Explicit companion device-identity override (naming spec
|
|
76
92
|
// 2026-08-24) — read straight off the flat ConfigOpts field, same
|
|
77
93
|
// shape as attachPinUi below. nil/absent falls through to the
|
|
@@ -87,6 +103,9 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
|
|
|
87
103
|
// badge) exist, mirroring `companionAttachPinUi` below.
|
|
88
104
|
cfg.companionBadgeEnabled = companionBadgeEnabled
|
|
89
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
|
|
90
109
|
cfg.companionBadgePosition = companionBadgePosition as String?
|
|
91
110
|
// Inline reporter theme (reporter branding spec 2026-08-25, RN
|
|
92
111
|
// slice). The .mm collapses the 8 flat ConfigOpts `theme*` wire
|
|
@@ -106,57 +125,40 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
|
|
|
106
125
|
if let vitalsEnabled { cfg.vitals.enabled = vitalsEnabled.boolValue }
|
|
107
126
|
if let vitalsSampleRate { cfg.vitals.sampleRate = vitalsSampleRate.doubleValue }
|
|
108
127
|
if let vitalsCaptureSourceQuery { cfg.vitals.captureSourceQuery = vitalsCaptureSourceQuery.boolValue }
|
|
128
|
+
// Absent stays nil, so the native SDK detects a debug build itself; present wins.
|
|
129
|
+
cfg.debugBuild = debugBuild?.boolValue
|
|
109
130
|
// The attach-PIN UI mode for startCompanion() to read (spec 2026-08-19). configure()
|
|
110
131
|
// precedes startCompanion() in every host's lifecycle, so this is the natural place to
|
|
111
132
|
// parse the flat ConfigOpts string into the native enum; startCompanion is where it is
|
|
112
133
|
// actually consumed (RelayWSClient's announce arg + the suppression seam), since a mode
|
|
113
|
-
// can't affect anything before a relay client exists.
|
|
114
|
-
//
|
|
134
|
+
// can't affect anything before a relay client exists. A changed value forces native
|
|
135
|
+
// reconfiguration even when the SDK configuration itself compares equal.
|
|
115
136
|
let newAttachPinUi = Self.parseAttachPinUi(attachPinUi as String?)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
// nothing. An identical configuration installs identical state, so the cheapest correct
|
|
124
|
-
// answer is not to start at all.
|
|
125
|
-
//
|
|
126
|
-
// Round-5 kept a cached snapshot of the options it last started with, and a cache can
|
|
127
|
-
// disagree with the singleton it claims to describe: two overlapping configures (A then
|
|
128
|
-
// B) can interleave so the cache says A while the SDK runs B, and a native host calling
|
|
129
|
-
// `Everframe.shared.start(configB)` between two bridge configures of A reproduces it with
|
|
130
|
-
// no concurrency at all — the second A matches the stale cached A, skips, and leaves B
|
|
131
|
-
// installed under a JS host that believes it configured A. `currentConfig` cannot drift:
|
|
132
|
-
// it IS the installed state.
|
|
133
|
-
//
|
|
134
|
-
// `captureGate` is the second term: a killed SDK must be restarted even by an identical
|
|
135
|
-
// config, or the host is left with a dead SDK and no way to revive it.
|
|
136
|
-
//
|
|
137
|
-
// Codex round-7, I3 — the comparison is the WHOLE config (`EverframeConfig` is now
|
|
138
|
-
// `Equatable`, matching Android's `data class`), not a rendering of the fields this
|
|
139
|
-
// bridge happens to set. Round-6 hand-built a string snapshot here, which meant any
|
|
140
|
-
// config field the snapshot's author had not listed — a new one, or one a native host
|
|
141
|
-
// set directly — read as "unchanged" and silently suppressed a start that was needed.
|
|
142
|
-
// `attachPinUi` is the one term that is NOT part of the config (it lands in module
|
|
143
|
-
// state), so it is compared separately.
|
|
144
|
-
//
|
|
145
|
-
// Everything else this method does still runs on the skip path — the presenter-resolver
|
|
146
|
-
// install and the crash-metadata prime — because both are idempotent and neither
|
|
147
|
-
// depends on a fresh start. The attach-PIN stash is the exception, and only nominally:
|
|
148
|
-
// on the skip path it would write the value it already holds.
|
|
149
|
-
let unchanged = Everframe.shared.captureGate
|
|
150
|
-
&& Everframe.shared.currentConfig == cfg
|
|
151
|
-
&& companionAttachPinUi == newAttachPinUi
|
|
152
|
-
if unchanged {
|
|
153
|
-
os_log("[everframe] configure: SDK already running this exact config — skipping start()",
|
|
154
|
-
log: log, type: .debug)
|
|
137
|
+
let health: ReleaseHealthConfiguration?
|
|
138
|
+
if releaseHealthEnabled, let loaded = releaseHealthLoadedBuildId {
|
|
139
|
+
// The SDK reads the app version itself; the bridge carries the loaded bundle, the user ID and
|
|
140
|
+
// whether debug builds count. A nil value (disabled, no loaded bundle, or an invalid identity) is off.
|
|
141
|
+
health = try? ReleaseHealthConfiguration(userId: releaseHealthUserId as String?,
|
|
142
|
+
loadedBuildId: loaded as String, loadedBundleStatus: .known,
|
|
143
|
+
includeDebugBuilds: releaseHealthIncludeDebugBuilds)
|
|
155
144
|
} else {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
145
|
+
health = nil
|
|
146
|
+
}
|
|
147
|
+
// The bridge owns release health through `health` alone: it needs the loaded Hermes bundle, and a new
|
|
148
|
+
// user ID must reconfigure health without restarting the SDK. The native default
|
|
149
|
+
// (`EverframeConfig.releaseHealth`, on) must not arm anonymous sessions at start or take part in
|
|
150
|
+
// the unchanged-configuration comparison.
|
|
151
|
+
cfg.releaseHealth = nil
|
|
152
|
+
// Native code atomically checks installed config and reserves its exact owner.
|
|
153
|
+
// An identical configure preserves the session; a changed attach-PIN mode still
|
|
154
|
+
// requires the same full restart as the other native configuration changes.
|
|
155
|
+
let applyHealth = try Everframe.shared.configureAndPrepareReleaseHealth(
|
|
156
|
+
config: cfg, health: health, forceRestart: companionAttachPinUi != newAttachPinUi)
|
|
157
|
+
companionAttachPinUi = newAttachPinUi
|
|
158
|
+
// Reservation is tied to this configure's exact native start generation. Later
|
|
159
|
+
// configure/disable or a native restart invalidates the asynchronous operation.
|
|
160
|
+
if let applyHealth {
|
|
161
|
+
Task { _ = await applyHealth() }
|
|
160
162
|
}
|
|
161
163
|
installPresenterResolverIfNeeded()
|
|
162
164
|
// Spec 2026-09-17 setExtra-resolver — installed unconditionally,
|
|
@@ -219,9 +221,9 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
|
|
|
219
221
|
/// Wires `EFReporterPresenter` as the resolver for `Everframe.shared.report
|
|
220
222
|
/// .open()`. Called from configure(); the first call installs, subsequent
|
|
221
223
|
/// calls no-op. tvOS has no on-device modal — reporting goes through the
|
|
222
|
-
/// phone-companion flow (QR → phone browser) — so
|
|
223
|
-
///
|
|
224
|
-
///
|
|
224
|
+
/// phone-companion flow (QR → phone browser) — so EverframeReporterUI's
|
|
225
|
+
/// tvOS `installResolver()` is a no-op. The gate here skips that call and
|
|
226
|
+
/// logs where tvOS reporting goes instead.
|
|
225
227
|
private static func installPresenterResolverIfNeeded() {
|
|
226
228
|
#if !os(tvOS)
|
|
227
229
|
resolverInstallLock.lock()
|
|
@@ -278,7 +280,7 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
|
|
|
278
280
|
/// every `configure()` is cheap, harmless churn, same as Android already
|
|
279
281
|
/// accepts on every module construction.
|
|
280
282
|
///
|
|
281
|
-
/// Returns the fresh generation so `EverframeModule.mm`'s `-
|
|
283
|
+
/// Returns the fresh generation so `EverframeModule.mm`'s `-configureSync:`
|
|
282
284
|
/// can capture it (via `currentExtraResolveHookGeneration()` below) and
|
|
283
285
|
/// hand it back to `resetExtraResolverForTeardown(_:)` at `-invalidate`
|
|
284
286
|
/// time. `@discardableResult` because `configure()` itself has no use
|
|
@@ -295,8 +297,8 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
|
|
|
295
297
|
|
|
296
298
|
/// ObjC-visible read of whichever generation is live RIGHT NOW — i.e.
|
|
297
299
|
/// whatever the most recent `installExtraResolveHook()` call minted.
|
|
298
|
-
/// `EverframeModule.mm`'s `-
|
|
299
|
-
/// `
|
|
300
|
+
/// `EverframeModule.mm`'s `-configureSync:` calls this immediately after
|
|
301
|
+
/// `configureWithSdkKey:...:error:` (which, on success or on the
|
|
300
302
|
/// already-running-unchanged skip path, calls `installExtraResolveHook()`
|
|
301
303
|
/// internally) and stashes the result on its own instance ivar for
|
|
302
304
|
/// `-invalidate` to hand back later. 0 means no `configure()` call has
|
|
@@ -400,7 +402,7 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
|
|
|
400
402
|
/// `RemoteVitalsBridge.reset()`'s own lifetime doc comment.
|
|
401
403
|
///
|
|
402
404
|
/// IDENTITY GUARD (D1 fix — stale-teardown race). `generation` is the
|
|
403
|
-
/// value `EverframeModule.mm`'s `-
|
|
405
|
+
/// value `EverframeModule.mm`'s `-configureSync:` captured via
|
|
404
406
|
/// `currentExtraResolveHookGeneration()` right after ITS OWN
|
|
405
407
|
/// `configure()` call, stashed on that ObjC instance and handed back
|
|
406
408
|
/// here at `-invalidate` time. RN dispatches `-invalidate` on a
|
|
@@ -562,12 +564,29 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
|
|
|
562
564
|
dict = ["status": "cancelled"]
|
|
563
565
|
}
|
|
564
566
|
completion(dict, nil)
|
|
567
|
+
} catch EverframeReporterError.reporterDisabled {
|
|
568
|
+
completion(nil, NSError(
|
|
569
|
+
domain: "EverframeBridge", code: reporterDisabledErrorCode,
|
|
570
|
+
userInfo: [NSLocalizedDescriptionKey: EverframeReporterError.reporterDisabled.localizedDescription]
|
|
571
|
+
))
|
|
565
572
|
} catch {
|
|
566
573
|
completion(nil, error as NSError)
|
|
567
574
|
}
|
|
568
575
|
}
|
|
569
576
|
}
|
|
570
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
|
+
|
|
571
590
|
// MARK: - registerSensitiveRect
|
|
572
591
|
|
|
573
592
|
/// Mark a view as sensitive. Routed through MainActor.assumeIsolated so the
|
|
@@ -604,12 +623,31 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
|
|
|
604
623
|
/// `everframe-react-native` (distinct from `CrashReporter.captureFacts`'s
|
|
605
624
|
/// `"everframe-ios"` default) so envelopes are attributable to the RN SDK.
|
|
606
625
|
@objc public static func reportCrash(_ crashJson: NSString) -> Bool {
|
|
607
|
-
|
|
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)
|
|
608
633
|
}
|
|
609
634
|
|
|
610
|
-
/// 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.
|
|
611
637
|
@objc public static func captureHandledException(_ crashJson: NSString) -> Bool {
|
|
612
|
-
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
|
|
613
651
|
}
|
|
614
652
|
|
|
615
653
|
// MARK: - setExtra
|
|
@@ -631,7 +669,7 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
|
|
|
631
669
|
/// `Everframe.shared.addBreadcrumb(message:kind:level:data:)` (Task 5) —
|
|
632
670
|
/// NO validation/coercion happens here. That singleton already owns
|
|
633
671
|
/// kind/level/data coercion AND gating (no-op pre-start / while killed),
|
|
634
|
-
/// exactly like `setExtra
|
|
672
|
+
/// exactly like `setExtra` above never re-checks
|
|
635
673
|
/// `captureGate` themselves. `data` crosses as an `NSDictionary?` — the
|
|
636
674
|
/// codegen-typed `UnsafeObject` boundary for freeform objects; a
|
|
637
675
|
/// non-dictionary payload (shouldn't happen via codegen, but fail-soft
|
|
@@ -655,8 +693,8 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
|
|
|
655
693
|
|
|
656
694
|
/// Forward a host-declared user from JS into `Everframe.shared.setUser`.
|
|
657
695
|
/// Pure pass-through: the native singleton owns storage — NO
|
|
658
|
-
/// validation/coercion happens here, same contract as `setExtra
|
|
659
|
-
///
|
|
696
|
+
/// validation/coercion happens here, same contract as `setExtra`
|
|
697
|
+
/// above. `nil` clears the active user (the bridge
|
|
660
698
|
/// forbids `T | null`, so JS expresses "clear" by calling with no
|
|
661
699
|
/// argument, which crosses as `nil` here). A missing key on the incoming
|
|
662
700
|
/// dictionary must become `nil`, never an empty string — that's what
|
|
@@ -749,9 +787,7 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
|
|
|
749
787
|
nonisolated(unsafe) private static var companionClient: RelayWSClient?
|
|
750
788
|
/// The host's configured attach-PIN UI mode (spec 2026-08-19), parsed
|
|
751
789
|
/// from `ConfigOpts.attachPinUi` at `configure()` time. Defaults to
|
|
752
|
-
/// `.builtin`
|
|
753
|
-
/// set the flag (including every host on an older RN SDK version whose
|
|
754
|
-
/// `configure()` call carries no such field at all).
|
|
790
|
+
/// `.builtin` for a host that never sets the flag.
|
|
755
791
|
nonisolated(unsafe) private static var companionAttachPinUi: AttachPinUi = .builtin
|
|
756
792
|
// `CompanionCaptureBridge` subscribes to the report.request notification
|
|
757
793
|
// posted by RelayWSClient and runs the capture+send dance. Without an
|
|
@@ -853,10 +889,10 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
|
|
|
853
889
|
/// presence", never "no socket": RelayWSClient skips the announce hop
|
|
854
890
|
/// entirely on a nil key and reporting is unaffected.
|
|
855
891
|
private static func configuredSdkKey() -> String? {
|
|
856
|
-
guard let
|
|
857
|
-
!
|
|
892
|
+
guard let sdkKey = Everframe.shared.currentConfig?.sdkKey,
|
|
893
|
+
!sdkKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
858
894
|
else { return nil }
|
|
859
|
-
return
|
|
895
|
+
return sdkKey
|
|
860
896
|
}
|
|
861
897
|
|
|
862
898
|
/// Non-personal device label for the dashboard's device list — "Apple TV",
|
|
@@ -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.
|
|
74
|
-
///
|
|
75
|
-
///
|
|
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
|
-
// •
|
|
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 `-
|
|
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 `-
|
|
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,11 +106,13 @@ RCT_EXPORT_MODULE(Everframe)
|
|
|
106
106
|
_bridge = bridge;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
// MARK: -
|
|
109
|
+
// MARK: - configureSync(opts)
|
|
110
110
|
|
|
111
111
|
// Signature MUST match the codegen-emitted protocol in EverframeSpec.h:
|
|
112
|
-
// - (
|
|
113
|
-
//
|
|
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.
|
|
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.
|
|
116
118
|
// The ingest URL is no longer JS-supplied — the native SDK bakes it at
|
|
@@ -120,16 +122,18 @@ 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
|
-
- (
|
|
124
|
-
NSString *
|
|
125
|
+
- (NSNumber *)configureSync:(JS::NativeEverframe::ConfigOpts &)opts {
|
|
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
|
|
127
129
|
// <bool>`, same shape as `networkBodiesDisabled()` above. Absent means the
|
|
128
130
|
// host said nothing, which is "send it": the SDK default is ON.
|
|
129
131
|
BOOL installIdentifierDisabled = opts.installIdentifierDisabled().value_or(false);
|
|
132
|
+
// `crashReportingDisabled` — same `std::optional<bool>` shape. Absent keeps capture on.
|
|
133
|
+
BOOL crashReportingDisabled = opts.crashReportingDisabled().value_or(false);
|
|
130
134
|
// `attachPinUi` (spec 2026-08-19) is a plain optional STRING field —
|
|
131
135
|
// `opts.attachPinUi()` returns a nullable NSString*, same shape as
|
|
132
|
-
// `opts.
|
|
136
|
+
// `opts.sdkKey()` above, NOT the `std::optional<bool>` shape
|
|
133
137
|
// `networkBodiesDisabled()` uses. `EverframeBridge.configure` coerces a
|
|
134
138
|
// nil/unrecognised value to `.builtin`.
|
|
135
139
|
NSString *attachPinUi = opts.attachPinUi();
|
|
@@ -149,6 +153,9 @@ RCT_EXPORT_MODULE(Everframe)
|
|
|
149
153
|
// above; `EverframeBridge` coerces nil/unrecognised to `.bottomRight`.
|
|
150
154
|
BOOL companionBadgeEnabled = opts.companionBadgeEnabled().value_or(true);
|
|
151
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);
|
|
152
159
|
NSString *companionBadgePosition = opts.companionBadgePosition();
|
|
153
160
|
// Inline reporter theme (reporter branding spec 2026-08-25, RN slice) —
|
|
154
161
|
// 8 flat optional STRING fields on ConfigOpts (RN codegen cannot express
|
|
@@ -179,11 +186,17 @@ RCT_EXPORT_MODULE(Everframe)
|
|
|
179
186
|
NSNumber *vitalsEnabled = opts.vitalsEnabled().has_value() ? @(opts.vitalsEnabled().value()) : nil;
|
|
180
187
|
NSNumber *vitalsSampleRate = opts.vitalsSampleRate().has_value() ? @(opts.vitalsSampleRate().value()) : nil;
|
|
181
188
|
NSNumber *vitalsCaptureSourceQuery = opts.vitalsCaptureSourceQuery().has_value() ? @(opts.vitalsCaptureSourceQuery().value()) : nil;
|
|
189
|
+
// `debugBuild` stays nil when absent so the native SDK decides for itself; the JS facade always sends it.
|
|
190
|
+
NSNumber *debugBuild = opts.debugBuild().has_value() ? @(opts.debugBuild().value()) : nil;
|
|
191
|
+
// Release health is on by default: a missing `releaseHealthEnabled` is on and only an explicit NO is
|
|
192
|
+
// off. The JS facade always sends the flag, so a missing value can only come from another caller.
|
|
193
|
+
// `releaseHealthIncludeDebugBuilds` is false when missing, matching the native SDK default.
|
|
182
194
|
NSError *err = nil;
|
|
183
|
-
[EverframeBridge
|
|
195
|
+
[EverframeBridge configureWithSdkKey:sdkKey
|
|
184
196
|
endpoint:@""
|
|
185
197
|
networkBodiesDisabled:networkBodiesDisabled
|
|
186
198
|
installIdentifierDisabled:installIdentifierDisabled
|
|
199
|
+
crashReportingDisabled:crashReportingDisabled
|
|
187
200
|
attachPinUi:attachPinUi
|
|
188
201
|
companionDeviceId:companionDeviceId
|
|
189
202
|
companionBadgeEnabled:companionBadgeEnabled
|
|
@@ -193,9 +206,15 @@ RCT_EXPORT_MODULE(Everframe)
|
|
|
193
206
|
vitalsEnabled:vitalsEnabled
|
|
194
207
|
vitalsSampleRate:vitalsSampleRate
|
|
195
208
|
vitalsCaptureSourceQuery:vitalsCaptureSourceQuery
|
|
209
|
+
debugBuild:debugBuild
|
|
210
|
+
releaseHealthEnabled:opts.releaseHealthEnabled().value_or(true)
|
|
211
|
+
releaseHealthLoadedBuildId:opts.releaseHealthLoadedBuildId()
|
|
212
|
+
releaseHealthUserId:opts.releaseHealthUserId()
|
|
213
|
+
releaseHealthIncludeDebugBuilds:opts.releaseHealthIncludeDebugBuilds().value_or(false)
|
|
214
|
+
reporterDisabled:reporterDisabled
|
|
196
215
|
error:&err];
|
|
197
216
|
if (err != nil) {
|
|
198
|
-
//
|
|
217
|
+
// A refused configuration is logged; JS keeps its mount either way.
|
|
199
218
|
RCTLogWarn(@"[everframe] configure failed: %@", err.localizedDescription);
|
|
200
219
|
}
|
|
201
220
|
// D1 fix (stale-teardown identity guard, spec 2026-09-17 setExtra-resolver).
|
|
@@ -211,13 +230,6 @@ RCT_EXPORT_MODULE(Everframe)
|
|
|
211
230
|
// the effective options are unchanged and the SDK is already started, and when it does
|
|
212
231
|
// restart, the JS `configureEpoch` bump re-runs every vitals hook effect — each one
|
|
213
232
|
// detaching its old token and tracking a fresh one against the SDK that is now current.
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Additive synchronous startup entry. The legacy void selector above stays
|
|
217
|
-
// ABI-compatible for already-compiled callers; current JS uses this boolean
|
|
218
|
-
// method so its first synchronous capture cannot overtake configure dispatch.
|
|
219
|
-
- (NSNumber *)configureSync:(JS::NativeEverframe::ConfigOpts &)opts {
|
|
220
|
-
[self configure:opts];
|
|
221
233
|
return @YES;
|
|
222
234
|
}
|
|
223
235
|
|
|
@@ -229,7 +241,9 @@ RCT_EXPORT_MODULE(Everframe)
|
|
|
229
241
|
reject:(RCTPromiseRejectBlock)reject {
|
|
230
242
|
[EverframeBridge openReporterWithCompletion:^(NSDictionary *result, NSError *err) {
|
|
231
243
|
if (err) {
|
|
232
|
-
|
|
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);
|
|
233
247
|
} else {
|
|
234
248
|
resolve(result ?: @{});
|
|
235
249
|
}
|
|
@@ -390,7 +404,7 @@ RCT_EXPORT_MODULE(Everframe)
|
|
|
390
404
|
// full 250ms ask-and-wait for an ack nobody will ever send.
|
|
391
405
|
//
|
|
392
406
|
// D1 fix: `_extraResolveHookGeneration` (captured at the end of
|
|
393
|
-
// `-
|
|
407
|
+
// `-configureSync:`) travels with this instance so the Swift side can tell a
|
|
394
408
|
// STALE instance's `-invalidate` — delivered late, on this module's
|
|
395
409
|
// method queue, after a reloaded bundle's fresh instance already
|
|
396
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.1
|
|
3
|
+
"version": "1.2.1",
|
|
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.1
|
|
103
|
+
"@everframe/react": "^1.2.1"
|
|
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/sdk-core": "0.
|
|
116
|
-
"@everframe/protocol": "0.
|
|
115
|
+
"@everframe/sdk-core": "0.6.0",
|
|
116
|
+
"@everframe/protocol": "0.7.0"
|
|
117
117
|
},
|
|
118
118
|
"codegenConfig": {
|
|
119
119
|
"name": "EverframeSpec",
|