@everframe/react-native 1.0.0 → 1.2.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 +5 -1
- package/README.md +295 -7
- package/android/build.gradle.kts +7 -6
- package/android/src/main/java/dev/everframe/rn/EverframeModule.kt +80 -49
- package/android/src/main/java/dev/everframe/rn/ReporterBridge.kt +89 -0
- package/dist/NativeEverframe.d.ts +26 -1
- package/dist/chunk-ENPQ66EV.js +1 -0
- package/dist/chunk-POE5CSO5.js +1 -0
- package/dist/index.d.ts +451 -67
- package/dist/index.js +31 -25
- package/dist/index.web.d.ts +12 -0
- 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.d.ts +15 -0
- package/dist/integrations/react.js +1 -0
- package/dist/promise-rejection-types-C-lxCdDl.d.ts +101 -0
- package/ios/Sources/EverframeBridge.swift +58 -56
- package/ios/Sources/EverframeModule.mm +21 -4
- package/package.json +9 -4
- package/src/NativeEverframe.ts +28 -2
- package/dist/chunk-QRPINMCR.js +0 -1
package/dist/index.web.d.ts
CHANGED
|
@@ -1 +1,13 @@
|
|
|
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
3
|
export * from '@everframe/react';
|
|
4
|
+
|
|
5
|
+
/** Hermes observation is separate from the browser SDK's own rejection handling. */
|
|
6
|
+
declare function getPromiseRejectionStatus(): PromiseRejectionStatus;
|
|
7
|
+
|
|
8
|
+
/** Mobile admission diagnostics are unavailable on the browser entry. */
|
|
9
|
+
declare function getErrorCaptureStatus(): ErrorCaptureStatus;
|
|
10
|
+
|
|
11
|
+
declare function getReportDeliveryStatus(): ReportDeliveryStatus;
|
|
12
|
+
|
|
13
|
+
export { ErrorCaptureStatus, PromiseRejectionStatus, ReportDeliveryStatus, getErrorCaptureStatus, getPromiseRejectionStatus, getReportDeliveryStatus };
|
package/dist/index.web.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export*from"@everframe/react";
|
|
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 +1 @@
|
|
|
1
|
-
import{
|
|
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 +1 @@
|
|
|
1
|
-
import{
|
|
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};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Call from an application's componentDidCatch, in a boundary rendered inside
|
|
3
|
+
* EverframeProvider. A boundary that wraps the provider replaces it with its
|
|
4
|
+
* fallback in the same commit, before componentDidCatch runs, so its calls find
|
|
5
|
+
* no provider unless that fallback mounts one. Inside the provider, errors
|
|
6
|
+
* caught during its first commit are not captured: the provider publishes its
|
|
7
|
+
* runtime after its descendants' componentDidCatch.
|
|
8
|
+
* The application owns its fallback and recovery policy. Native acceptance does
|
|
9
|
+
* not establish delivery; fatal escalation can still produce a separate report.
|
|
10
|
+
*/
|
|
11
|
+
declare function captureReactError(error: unknown, info?: {
|
|
12
|
+
componentStack?: string | null;
|
|
13
|
+
}): void;
|
|
14
|
+
|
|
15
|
+
export { captureReactError };
|
|
@@ -0,0 +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};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
declare const capturePaths: readonly ["native-handled", "bridge-handled", "bridge-automatic", "jvm-uncaught"];
|
|
2
|
+
declare const captureOutcomes: readonly ["persisted", "disabled", "admission-suppressed", "reentrant", "invalid-input", "ownership-lost", "storage-unavailable", "failed"];
|
|
3
|
+
declare const queueOperations: readonly ["enqueue-committed", "enqueue-failed", "capacity-evicted", "removed-after-acceptance", "removed-after-terminal", "removal-failed", "read-failed"];
|
|
4
|
+
declare const storageFailures: readonly ["capacity", "key-unavailable", "corrupt", "io", "revoked", "invalid-entry", "busy", "unknown", "unsupported-format"];
|
|
5
|
+
declare const transportOrigins: readonly ["live-submit", "outbox-drain"];
|
|
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';
|
|
14
|
+
interface ReportCapturePathStatus {
|
|
15
|
+
supported: boolean;
|
|
16
|
+
settledAttempts: number;
|
|
17
|
+
outcomes: Record<ReportCaptureOutcome, number>;
|
|
18
|
+
lastOutcome?: ReportCaptureOutcome;
|
|
19
|
+
}
|
|
20
|
+
interface ReportQueueStatus {
|
|
21
|
+
scope: 'sdk-report-outbox';
|
|
22
|
+
observation: 'not-observed' | 'observed' | 'failed';
|
|
23
|
+
quality: 'complete' | 'partial' | 'unknown';
|
|
24
|
+
pendingCount?: number;
|
|
25
|
+
capacityPolicy: 'reject-new' | 'evict-oldest' | 'unknown';
|
|
26
|
+
terminalHttpPolicy: 'retain' | 'attempt-remove' | 'unknown';
|
|
27
|
+
operations: Record<ReportQueueOperation, number>;
|
|
28
|
+
lastFailure?: ReportStorageFailure;
|
|
29
|
+
migration: 'not-observed' | 'clear' | 'blocked' | 'unknown';
|
|
30
|
+
}
|
|
31
|
+
interface ReportTransportStatus {
|
|
32
|
+
settledAttempts: number;
|
|
33
|
+
outcomes: Record<ReportTransportOutcome, number>;
|
|
34
|
+
lastOutcome?: ReportTransportOutcome;
|
|
35
|
+
lastHttpStatus?: number;
|
|
36
|
+
}
|
|
37
|
+
interface ReportDeliveryStatus {
|
|
38
|
+
schemaVersion: 1;
|
|
39
|
+
status: 'active' | 'not-started' | 'disabled' | 'not-mounted' | 'unsupported' | 'unavailable';
|
|
40
|
+
reason: ReportDeliveryReason;
|
|
41
|
+
scope: 'native-runtime-observations';
|
|
42
|
+
coverage: 'best-effort';
|
|
43
|
+
revision: number;
|
|
44
|
+
capture: {
|
|
45
|
+
enabled: boolean;
|
|
46
|
+
paths: Record<ReportCapturePath, ReportCapturePathStatus>;
|
|
47
|
+
};
|
|
48
|
+
queue: ReportQueueStatus;
|
|
49
|
+
transport: Record<ReportTransportOrigin, ReportTransportStatus>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Local admission only: native acceptance is not delivery confirmation. */
|
|
53
|
+
type ErrorCapturePath = 'handled' | 'errorUtils' | 'rejection';
|
|
54
|
+
interface ErrorCaptureCounters {
|
|
55
|
+
attempted: number;
|
|
56
|
+
accepted: number;
|
|
57
|
+
duplicateSuppressed: number;
|
|
58
|
+
allowanceSuppressed: number;
|
|
59
|
+
bridgeUnavailable: number;
|
|
60
|
+
nativeRefused: number;
|
|
61
|
+
captureFailed: number;
|
|
62
|
+
reentrantSuppressed: number;
|
|
63
|
+
inactiveAborted: number;
|
|
64
|
+
legacyAttempted: number;
|
|
65
|
+
}
|
|
66
|
+
type ErrorCaptureOutcome = Exclude<keyof ErrorCaptureCounters, 'attempted'>;
|
|
67
|
+
interface ErrorCaptureStatus {
|
|
68
|
+
status: 'not-mounted' | 'disabled' | 'active' | 'unsupported';
|
|
69
|
+
reason: 'no-mount' | 'crash-reporting-disabled' | 'none' | 'platform';
|
|
70
|
+
scope: 'mounted-js-admission';
|
|
71
|
+
limits: {
|
|
72
|
+
handled: 10;
|
|
73
|
+
automatic: 10;
|
|
74
|
+
};
|
|
75
|
+
counters: Record<ErrorCapturePath, ErrorCaptureCounters>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
type RejectionReason = 'none' | 'opt-in-required' | 'crash-reporting-disabled' | 'platform' | 'runtime' | 'promise-identity' | 'hook-shape' | 'hook-install' | 'hook-displaced' | 'no-mount';
|
|
79
|
+
interface PromiseRejectionCounters {
|
|
80
|
+
pending: number;
|
|
81
|
+
accepted: number;
|
|
82
|
+
cancelled: number;
|
|
83
|
+
capacityDropped: number;
|
|
84
|
+
sizeDropped: number;
|
|
85
|
+
expired: number;
|
|
86
|
+
duplicateSuppressed: number;
|
|
87
|
+
allowanceSuppressed: number;
|
|
88
|
+
nativeRefused: number;
|
|
89
|
+
captureFailed: number;
|
|
90
|
+
/** Pending observations released when another owner replaced the hooks. */
|
|
91
|
+
discarded: number;
|
|
92
|
+
}
|
|
93
|
+
interface PromiseRejectionStatus {
|
|
94
|
+
status: 'disabled' | 'unsupported' | 'observing' | 'displaced' | 'install-failed' | 'not-mounted';
|
|
95
|
+
reason: RejectionReason;
|
|
96
|
+
adapterId?: string;
|
|
97
|
+
previousCallbacksPresent: boolean;
|
|
98
|
+
counters: PromiseRejectionCounters;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type { ErrorCaptureStatus as E, PromiseRejectionStatus as P, ReportDeliveryStatus as R, ErrorCaptureCounters as a, ErrorCaptureOutcome as b, ErrorCapturePath as c, PromiseRejectionCounters as d, RejectionReason as e, ReportCaptureOutcome as f, ReportCapturePath as g, ReportCapturePathStatus as h, ReportDeliveryReason as i, ReportQueueOperation as j, ReportQueueStatus as k, ReportStorageFailure as l, ReportTransportOrigin as m, ReportTransportOutcome as n, ReportTransportStatus as o };
|
|
@@ -44,11 +44,15 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
|
|
|
44
44
|
|
|
45
45
|
// MARK: - configure
|
|
46
46
|
|
|
47
|
+
// Serialize the bridge's start/idempotence decision with synchronous health reservation.
|
|
48
|
+
private static let configureLock = NSRecursiveLock()
|
|
49
|
+
|
|
47
50
|
@objc public static func configure(
|
|
48
|
-
|
|
51
|
+
sdkKey: NSString,
|
|
49
52
|
endpoint: NSString,
|
|
50
53
|
networkBodiesDisabled: Bool,
|
|
51
54
|
installIdentifierDisabled: Bool,
|
|
55
|
+
crashReportingDisabled: Bool,
|
|
52
56
|
attachPinUi: NSString?,
|
|
53
57
|
companionDeviceId: NSString?,
|
|
54
58
|
companionBadgeEnabled: Bool,
|
|
@@ -57,21 +61,31 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
|
|
|
57
61
|
theme: NSDictionary?,
|
|
58
62
|
vitalsEnabled: NSNumber?,
|
|
59
63
|
vitalsSampleRate: NSNumber?,
|
|
60
|
-
vitalsCaptureSourceQuery: NSNumber
|
|
64
|
+
vitalsCaptureSourceQuery: NSNumber?,
|
|
65
|
+
debugBuild: NSNumber?,
|
|
66
|
+
releaseHealthEnabled: Bool,
|
|
67
|
+
releaseHealthLoadedBuildId: NSString?,
|
|
68
|
+
releaseHealthUserId: NSString?,
|
|
69
|
+
releaseHealthIncludeDebugBuilds: Bool
|
|
61
70
|
) throws {
|
|
71
|
+
configureLock.lock()
|
|
72
|
+
defer { configureLock.unlock() }
|
|
62
73
|
// `endpoint` arg is retained for ObjC ABI compatibility with the
|
|
63
74
|
// codegen .mm — value is ignored. The SDK bakes the ingest URL at
|
|
64
75
|
// compile time (sdk-ios IngestEndpoint).
|
|
65
76
|
_ = endpoint
|
|
66
|
-
os_log("[everframe] configure
|
|
77
|
+
os_log("[everframe] configure sdkKey=%{public}@", log: log, type: .info, sdkKey as String)
|
|
67
78
|
// Polarity flips exactly once, here: JS sends the veto
|
|
68
79
|
// (`networkBodiesDisabled`), native stores the permission.
|
|
69
|
-
var cfg = EverframeConfig(
|
|
80
|
+
var cfg = EverframeConfig(sdkKey: sdkKey as String)
|
|
70
81
|
cfg.capture.networkBodies = !networkBodiesDisabled
|
|
71
82
|
// Polarity flips exactly once, here: JS sends the veto
|
|
72
83
|
// (`installIdentifierDisabled`), native stores the permission —
|
|
73
84
|
// identical to `networkBodies` above.
|
|
74
85
|
cfg.installIdentifierEnabled = !installIdentifierDisabled
|
|
86
|
+
// Polarity flips exactly once, here: JS sends the veto
|
|
87
|
+
// (`crashReportingDisabled`), native stores the permission.
|
|
88
|
+
cfg.capture.crash = !crashReportingDisabled
|
|
75
89
|
// Explicit companion device-identity override (naming spec
|
|
76
90
|
// 2026-08-24) — read straight off the flat ConfigOpts field, same
|
|
77
91
|
// shape as attachPinUi below. nil/absent falls through to the
|
|
@@ -106,57 +120,40 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
|
|
|
106
120
|
if let vitalsEnabled { cfg.vitals.enabled = vitalsEnabled.boolValue }
|
|
107
121
|
if let vitalsSampleRate { cfg.vitals.sampleRate = vitalsSampleRate.doubleValue }
|
|
108
122
|
if let vitalsCaptureSourceQuery { cfg.vitals.captureSourceQuery = vitalsCaptureSourceQuery.boolValue }
|
|
123
|
+
// Absent stays nil, so the native SDK detects a debug build itself; present wins.
|
|
124
|
+
cfg.debugBuild = debugBuild?.boolValue
|
|
109
125
|
// The attach-PIN UI mode for startCompanion() to read (spec 2026-08-19). configure()
|
|
110
126
|
// precedes startCompanion() in every host's lifecycle, so this is the natural place to
|
|
111
127
|
// parse the flat ConfigOpts string into the native enum; startCompanion is where it is
|
|
112
128
|
// actually consumed (RelayWSClient's announce arg + the suppression seam), since a mode
|
|
113
|
-
// can't affect anything before a relay client exists.
|
|
114
|
-
//
|
|
129
|
+
// can't affect anything before a relay client exists. A changed value forces native
|
|
130
|
+
// reconfiguration even when the SDK configuration itself compares equal.
|
|
115
131
|
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)
|
|
132
|
+
let health: ReleaseHealthConfiguration?
|
|
133
|
+
if releaseHealthEnabled, let loaded = releaseHealthLoadedBuildId {
|
|
134
|
+
// The SDK reads the app version itself; the bridge carries the loaded bundle, the user ID and
|
|
135
|
+
// whether debug builds count. A nil value (disabled, no loaded bundle, or an invalid identity) is off.
|
|
136
|
+
health = try? ReleaseHealthConfiguration(userId: releaseHealthUserId as String?,
|
|
137
|
+
loadedBuildId: loaded as String, loadedBundleStatus: .known,
|
|
138
|
+
includeDebugBuilds: releaseHealthIncludeDebugBuilds)
|
|
155
139
|
} else {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
140
|
+
health = nil
|
|
141
|
+
}
|
|
142
|
+
// The bridge owns release health through `health` alone: it needs the loaded Hermes bundle, and a new
|
|
143
|
+
// user ID must reconfigure health without restarting the SDK. The native default
|
|
144
|
+
// (`EverframeConfig.releaseHealth`, on) must not arm anonymous sessions at start or take part in
|
|
145
|
+
// the unchanged-configuration comparison.
|
|
146
|
+
cfg.releaseHealth = nil
|
|
147
|
+
// Native code atomically checks installed config and reserves its exact owner.
|
|
148
|
+
// An identical configure preserves the session; a changed attach-PIN mode still
|
|
149
|
+
// requires the same full restart as the other native configuration changes.
|
|
150
|
+
let applyHealth = try Everframe.shared.configureAndPrepareReleaseHealth(
|
|
151
|
+
config: cfg, health: health, forceRestart: companionAttachPinUi != newAttachPinUi)
|
|
152
|
+
companionAttachPinUi = newAttachPinUi
|
|
153
|
+
// Reservation is tied to this configure's exact native start generation. Later
|
|
154
|
+
// configure/disable or a native restart invalidates the asynchronous operation.
|
|
155
|
+
if let applyHealth {
|
|
156
|
+
Task { _ = await applyHealth() }
|
|
160
157
|
}
|
|
161
158
|
installPresenterResolverIfNeeded()
|
|
162
159
|
// Spec 2026-09-17 setExtra-resolver — installed unconditionally,
|
|
@@ -219,9 +216,9 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
|
|
|
219
216
|
/// Wires `EFReporterPresenter` as the resolver for `Everframe.shared.report
|
|
220
217
|
/// .open()`. Called from configure(); the first call installs, subsequent
|
|
221
218
|
/// calls no-op. tvOS has no on-device modal — reporting goes through the
|
|
222
|
-
/// phone-companion flow (QR → phone browser) — so
|
|
223
|
-
///
|
|
224
|
-
///
|
|
219
|
+
/// phone-companion flow (QR → phone browser) — so EverframeReporterUI's
|
|
220
|
+
/// tvOS `installResolver()` is a no-op. The gate here skips that call and
|
|
221
|
+
/// logs where tvOS reporting goes instead.
|
|
225
222
|
private static func installPresenterResolverIfNeeded() {
|
|
226
223
|
#if !os(tvOS)
|
|
227
224
|
resolverInstallLock.lock()
|
|
@@ -296,7 +293,7 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
|
|
|
296
293
|
/// ObjC-visible read of whichever generation is live RIGHT NOW — i.e.
|
|
297
294
|
/// whatever the most recent `installExtraResolveHook()` call minted.
|
|
298
295
|
/// `EverframeModule.mm`'s `-configure:` calls this immediately after
|
|
299
|
-
/// `
|
|
296
|
+
/// `configureWithSdkKey:...:error:` (which, on success or on the
|
|
300
297
|
/// already-running-unchanged skip path, calls `installExtraResolveHook()`
|
|
301
298
|
/// internally) and stashes the result on its own instance ivar for
|
|
302
299
|
/// `-invalidate` to hand back later. 0 means no `configure()` call has
|
|
@@ -592,6 +589,11 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
|
|
|
592
589
|
}
|
|
593
590
|
}
|
|
594
591
|
|
|
592
|
+
/// Cached, content-free native delivery facts. Encoding failure is rejected by JS.
|
|
593
|
+
@objc public static func getReportDeliveryStatusJson() -> NSString {
|
|
594
|
+
((try? Everframe.shared.getReportDeliveryStatus().toJSON()) ?? "{}") as NSString
|
|
595
|
+
}
|
|
596
|
+
|
|
595
597
|
// MARK: - reportCrash (Task 13 — crash/error reporting spec 2026-07-18)
|
|
596
598
|
|
|
597
599
|
/// Crash/error reporting. Synchronous: the RN fatal path must finish
|
|
@@ -848,10 +850,10 @@ import EverframeReporterUI // tvOS slice present per Package.swift:12 (TV-03
|
|
|
848
850
|
/// presence", never "no socket": RelayWSClient skips the announce hop
|
|
849
851
|
/// entirely on a nil key and reporting is unaffected.
|
|
850
852
|
private static func configuredSdkKey() -> String? {
|
|
851
|
-
guard let
|
|
852
|
-
!
|
|
853
|
+
guard let sdkKey = Everframe.shared.currentConfig?.sdkKey,
|
|
854
|
+
!sdkKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
853
855
|
else { return nil }
|
|
854
|
-
return
|
|
856
|
+
return sdkKey
|
|
855
857
|
}
|
|
856
858
|
|
|
857
859
|
/// Non-personal device label for the dashboard's device list — "Apple TV",
|
|
@@ -110,7 +110,7 @@ RCT_EXPORT_MODULE(Everframe)
|
|
|
110
110
|
|
|
111
111
|
// Signature MUST match the codegen-emitted protocol in EverframeSpec.h:
|
|
112
112
|
// - (void)configure:(JS::NativeEverframe::ConfigOpts &)opts;
|
|
113
|
-
// Field accessors (.
|
|
113
|
+
// Field accessors (.sdkKey(), .sdkVersion(), .captureScreenshot(),
|
|
114
114
|
// .networkBodiesDisabled()) are inline methods on the JS::NativeEverframe::
|
|
115
115
|
// ConfigOpts struct, generated from the TS spec in src/NativeEverframe.ts.
|
|
116
116
|
// The ingest URL is no longer JS-supplied — the native SDK bakes it at
|
|
@@ -121,15 +121,17 @@ RCT_EXPORT_MODULE(Everframe)
|
|
|
121
121
|
// silently dropped on iOS today. Task 9 (spec 2026-08-12) only wires
|
|
122
122
|
// `networkBodiesDisabled` through; see task-9-report.md.
|
|
123
123
|
- (void)configure:(JS::NativeEverframe::ConfigOpts &)opts {
|
|
124
|
-
NSString *
|
|
124
|
+
NSString *sdkKey = opts.sdkKey() ?: @"";
|
|
125
125
|
BOOL networkBodiesDisabled = opts.networkBodiesDisabled().value_or(false);
|
|
126
126
|
// `installIdentifierDisabled` (MAI meter spec 2026-08-27) — `std::optional
|
|
127
127
|
// <bool>`, same shape as `networkBodiesDisabled()` above. Absent means the
|
|
128
128
|
// host said nothing, which is "send it": the SDK default is ON.
|
|
129
129
|
BOOL installIdentifierDisabled = opts.installIdentifierDisabled().value_or(false);
|
|
130
|
+
// `crashReportingDisabled` — same `std::optional<bool>` shape. Absent keeps capture on.
|
|
131
|
+
BOOL crashReportingDisabled = opts.crashReportingDisabled().value_or(false);
|
|
130
132
|
// `attachPinUi` (spec 2026-08-19) is a plain optional STRING field —
|
|
131
133
|
// `opts.attachPinUi()` returns a nullable NSString*, same shape as
|
|
132
|
-
// `opts.
|
|
134
|
+
// `opts.sdkKey()` above, NOT the `std::optional<bool>` shape
|
|
133
135
|
// `networkBodiesDisabled()` uses. `EverframeBridge.configure` coerces a
|
|
134
136
|
// nil/unrecognised value to `.builtin`.
|
|
135
137
|
NSString *attachPinUi = opts.attachPinUi();
|
|
@@ -179,11 +181,17 @@ RCT_EXPORT_MODULE(Everframe)
|
|
|
179
181
|
NSNumber *vitalsEnabled = opts.vitalsEnabled().has_value() ? @(opts.vitalsEnabled().value()) : nil;
|
|
180
182
|
NSNumber *vitalsSampleRate = opts.vitalsSampleRate().has_value() ? @(opts.vitalsSampleRate().value()) : nil;
|
|
181
183
|
NSNumber *vitalsCaptureSourceQuery = opts.vitalsCaptureSourceQuery().has_value() ? @(opts.vitalsCaptureSourceQuery().value()) : nil;
|
|
184
|
+
// `debugBuild` stays nil when absent so the native SDK decides for itself; the JS facade always sends it.
|
|
185
|
+
NSNumber *debugBuild = opts.debugBuild().has_value() ? @(opts.debugBuild().value()) : nil;
|
|
186
|
+
// Release health is on by default: a missing `releaseHealthEnabled` is on and only an explicit NO is
|
|
187
|
+
// off. The JS facade always sends the flag, so a missing value can only come from another caller.
|
|
188
|
+
// `releaseHealthIncludeDebugBuilds` is false when missing, matching the native SDK default.
|
|
182
189
|
NSError *err = nil;
|
|
183
|
-
[EverframeBridge
|
|
190
|
+
[EverframeBridge configureWithSdkKey:sdkKey
|
|
184
191
|
endpoint:@""
|
|
185
192
|
networkBodiesDisabled:networkBodiesDisabled
|
|
186
193
|
installIdentifierDisabled:installIdentifierDisabled
|
|
194
|
+
crashReportingDisabled:crashReportingDisabled
|
|
187
195
|
attachPinUi:attachPinUi
|
|
188
196
|
companionDeviceId:companionDeviceId
|
|
189
197
|
companionBadgeEnabled:companionBadgeEnabled
|
|
@@ -193,6 +201,11 @@ RCT_EXPORT_MODULE(Everframe)
|
|
|
193
201
|
vitalsEnabled:vitalsEnabled
|
|
194
202
|
vitalsSampleRate:vitalsSampleRate
|
|
195
203
|
vitalsCaptureSourceQuery:vitalsCaptureSourceQuery
|
|
204
|
+
debugBuild:debugBuild
|
|
205
|
+
releaseHealthEnabled:opts.releaseHealthEnabled().value_or(true)
|
|
206
|
+
releaseHealthLoadedBuildId:opts.releaseHealthLoadedBuildId()
|
|
207
|
+
releaseHealthUserId:opts.releaseHealthUserId()
|
|
208
|
+
releaseHealthIncludeDebugBuilds:opts.releaseHealthIncludeDebugBuilds().value_or(false)
|
|
196
209
|
error:&err];
|
|
197
210
|
if (err != nil) {
|
|
198
211
|
// Configure is void-typed at the spec; we can't reject a promise here.
|
|
@@ -419,6 +432,10 @@ RCT_EXPORT_MODULE(Everframe)
|
|
|
419
432
|
[EverframeBridge setUser:dict];
|
|
420
433
|
}
|
|
421
434
|
|
|
435
|
+
- (NSString *)getReportDeliveryStatusJson {
|
|
436
|
+
return [EverframeBridge getReportDeliveryStatusJson];
|
|
437
|
+
}
|
|
438
|
+
|
|
422
439
|
// MARK: - reportCrash(crashJson) — Task 13 (spec 2026-07-18)
|
|
423
440
|
|
|
424
441
|
// Sync spec method: codegen emits a non-void, non-promise boolean-returning
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@everframe/react-native",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.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",
|
|
@@ -73,6 +73,11 @@
|
|
|
73
73
|
"./integrations/theoplayer": {
|
|
74
74
|
"types": "./dist/integrations/theoplayer.d.ts",
|
|
75
75
|
"default": "./dist/integrations/theoplayer.js"
|
|
76
|
+
},
|
|
77
|
+
"./integrations/react": {
|
|
78
|
+
"types": "./dist/integrations/react.d.ts",
|
|
79
|
+
"browser": null,
|
|
80
|
+
"default": "./dist/integrations/react.js"
|
|
76
81
|
}
|
|
77
82
|
},
|
|
78
83
|
"files": [
|
|
@@ -95,7 +100,7 @@
|
|
|
95
100
|
"dependencies": {
|
|
96
101
|
"bippy": "^0.5.39",
|
|
97
102
|
"zod": "^4.3.0",
|
|
98
|
-
"@everframe/react": "^1.
|
|
103
|
+
"@everframe/react": "^1.2.0"
|
|
99
104
|
},
|
|
100
105
|
"devDependencies": {
|
|
101
106
|
"@arethetypeswrong/cli": "^0.18.0",
|
|
@@ -107,8 +112,8 @@
|
|
|
107
112
|
"publint": "^0.3.0",
|
|
108
113
|
"tsup": "^8.0.0",
|
|
109
114
|
"vitest": "^4.0.0",
|
|
110
|
-
"@everframe/
|
|
111
|
-
"@everframe/
|
|
115
|
+
"@everframe/protocol": "0.7.0",
|
|
116
|
+
"@everframe/sdk-core": "0.6.0"
|
|
112
117
|
},
|
|
113
118
|
"codegenConfig": {
|
|
114
119
|
"name": "EverframeSpec",
|
package/src/NativeEverframe.ts
CHANGED
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
// an absent field, which decodes as `undefined` and never reaches native
|
|
66
66
|
// at all — to `.builtin`/`BUILTIN`. This rides `configure()`, not a new
|
|
67
67
|
// bridge method: the flag is a startup-time preference, not a per-call
|
|
68
|
-
// argument, so it belongs beside `
|
|
68
|
+
// argument, so it belongs beside `sdkKey`/`networkBodiesDisabled` on
|
|
69
69
|
// `ConfigOpts` — see `companion.ts`'s `everframe.companion.attachChallenge`
|
|
70
70
|
// event, the JS-facing half of this same D-decision.
|
|
71
71
|
// Session Vitals (spec 2026-09-06) is the next D-decision after that —
|
|
@@ -105,7 +105,7 @@ export type Rect = {
|
|
|
105
105
|
* Rich variants (e.g., callbacks, function refs) MUST stay on the JS side.
|
|
106
106
|
*/
|
|
107
107
|
export type ConfigOpts = {
|
|
108
|
-
|
|
108
|
+
sdkKey?: string;
|
|
109
109
|
sdkVersion?: string;
|
|
110
110
|
/**
|
|
111
111
|
* If true, the native side captures a UIWindow/SurfaceFlinger screenshot
|
|
@@ -131,6 +131,14 @@ export type ConfigOpts = {
|
|
|
131
131
|
* once polarity is flipped.
|
|
132
132
|
*/
|
|
133
133
|
installIdentifierDisabled?: boolean;
|
|
134
|
+
/**
|
|
135
|
+
* Client veto for automatic crash capture. `true` turns off the native SDKs' crash capture
|
|
136
|
+
* (JVM, native crashes and ANRs on Android; native crashes on iOS) as well as the JS handlers:
|
|
137
|
+
* both natives store `capture.crash = !crashReportingDisabled`. Flat because RN codegen rejects
|
|
138
|
+
* nested objects; `extractBridgeConfig` flattens the host-facing `crashReporting: { disabled }`.
|
|
139
|
+
* Absent means capture stays on.
|
|
140
|
+
*/
|
|
141
|
+
crashReportingDisabled?: boolean;
|
|
134
142
|
/**
|
|
135
143
|
* Built-in attach-PIN surface mode (spec 2026-08-19): 'builtin' (default —
|
|
136
144
|
* the native SDK presents the code itself), 'custom' (host renders it from
|
|
@@ -210,6 +218,21 @@ export type ConfigOpts = {
|
|
|
210
218
|
vitalsEnabled?: boolean;
|
|
211
219
|
vitalsSampleRate?: number;
|
|
212
220
|
vitalsCaptureSourceQuery?: boolean;
|
|
221
|
+
/**
|
|
222
|
+
* Whether this is a debug build, which Everframe never makes a release out of. The JS facade always
|
|
223
|
+
* sends it (the config's `debugBuild`, else `__DEV__`). Absent means the native SDK decides for itself.
|
|
224
|
+
*/
|
|
225
|
+
debugBuild?: boolean;
|
|
226
|
+
/**
|
|
227
|
+
* Native foreground monitoring (release health). The JS facade always sends this flag: `true` with a
|
|
228
|
+
* validated loaded bundle, or `false` to revoke monitoring. A bridge that finds it missing treats it as
|
|
229
|
+
* on, and only an explicit `false` as off. The native SDK reads the app version.
|
|
230
|
+
*/
|
|
231
|
+
releaseHealthEnabled?: boolean;
|
|
232
|
+
releaseHealthLoadedBuildId?: string;
|
|
233
|
+
releaseHealthUserId?: string;
|
|
234
|
+
/** Count debug builds, which the native SDKs skip by default. Absent means false. */
|
|
235
|
+
releaseHealthIncludeDebugBuilds?: boolean;
|
|
213
236
|
};
|
|
214
237
|
|
|
215
238
|
/**
|
|
@@ -319,6 +342,9 @@ export interface Spec extends TurboModule {
|
|
|
319
342
|
*/
|
|
320
343
|
recordScreen(name: string, data?: UnsafeObject): void;
|
|
321
344
|
|
|
345
|
+
/** Additive diagnostic bridge: bounded JSON of cached native observations; no I/O. */
|
|
346
|
+
getReportDeliveryStatusJson(): string;
|
|
347
|
+
|
|
322
348
|
/**
|
|
323
349
|
* Crash/error reporting (spec 2026-07-18). SYNCHRONOUS by design (non-void
|
|
324
350
|
* return forces a sync TurboModule method): the fatal path must complete
|
package/dist/chunk-QRPINMCR.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a as r}from"./chunk-XUSV3KOD.js";var n=class extends Error{static{r(this,"EverframeNotMountedError")}name="EverframeNotMountedError";constructor(o){super(o??"EverframeProvider is not mounted; wrap your app root.")}};var t=null;function a(e){t=e}r(a,"__setCurrentContext");function d(){return t}r(d,"__getCurrentContext");function p(){let e=t;return e?e.open():Promise.reject(new n("EverframeProvider not mounted; wrap your root with <EverframeProvider>."))}r(p,"open");function c(e){t?.setExtra(e)}r(c,"setExtra");function v(e){t?.addBreadcrumb(e)}r(v,"addBreadcrumb");function x(e,o){o!==void 0?t?.recordScreen(e,o):t?.recordScreen(e)}r(x,"recordScreen");function m(e){t?.setUser(e)}r(m,"setUser");function f(e,o){t?.captureException(e,o)}r(f,"captureException");export{n as a,a as b,d as c,p as d,c as e,v as f,x as g,m as h,f as i};
|