@barrysolomon/mobile-react-native 0.1.0-alpha
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/Dash0Mobile.podspec +29 -0
- package/README.md +117 -0
- package/android/build.gradle +68 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/dash0/mobile/reactnative/BridgeCallSink.kt +67 -0
- package/android/src/main/java/com/dash0/mobile/reactnative/Dash0MobileModule.kt +198 -0
- package/android/src/main/java/com/dash0/mobile/reactnative/Dash0MobilePackage.kt +14 -0
- package/android/src/main/java/com/dash0/mobile/reactnative/OTelMobileCallSink.kt +208 -0
- package/android/src/test/java/com/dash0/mobile/reactnative/Dash0MobileModuleTest.kt +413 -0
- package/ios/BridgeCallSink.swift +83 -0
- package/ios/Dash0MobileBridgeDispatcher.swift +142 -0
- package/ios/OTelMobileCallSink.swift +262 -0
- package/ios/RCTDash0MobileModule.m +28 -0
- package/ios/RCTDash0MobileModule.swift +104 -0
- package/ios/Tests/Dash0MobileBridgeDispatcherTests.swift +341 -0
- package/lib/src/NativeDash0Mobile.d.ts +27 -0
- package/lib/src/NativeDash0Mobile.d.ts.map +1 -0
- package/lib/src/NativeDash0Mobile.js +19 -0
- package/lib/src/bridge/NativeBridge.d.ts +38 -0
- package/lib/src/bridge/NativeBridge.d.ts.map +1 -0
- package/lib/src/bridge/NativeBridge.js +95 -0
- package/lib/src/bridge/types.d.ts +166 -0
- package/lib/src/bridge/types.d.ts.map +1 -0
- package/lib/src/bridge/types.js +10 -0
- package/lib/src/index.d.ts +35 -0
- package/lib/src/index.d.ts.map +1 -0
- package/lib/src/index.js +408 -0
- package/lib/src/instrumentation/errors.d.ts +14 -0
- package/lib/src/instrumentation/errors.d.ts.map +1 -0
- package/lib/src/instrumentation/errors.js +65 -0
- package/lib/src/instrumentation/fetch.d.ts +16 -0
- package/lib/src/instrumentation/fetch.d.ts.map +1 -0
- package/lib/src/instrumentation/fetch.js +75 -0
- package/lib/src/instrumentation/navigation.d.ts +19 -0
- package/lib/src/instrumentation/navigation.d.ts.map +1 -0
- package/lib/src/instrumentation/navigation.js +39 -0
- package/lib/src/instrumentation/touch.d.ts +12 -0
- package/lib/src/instrumentation/touch.d.ts.map +1 -0
- package/lib/src/instrumentation/touch.js +18 -0
- package/lib/src/instrumentation/unhandledRejection.d.ts +9 -0
- package/lib/src/instrumentation/unhandledRejection.d.ts.map +1 -0
- package/lib/src/instrumentation/unhandledRejection.js +52 -0
- package/lib/src/instrumentation/xhr.d.ts +14 -0
- package/lib/src/instrumentation/xhr.d.ts.map +1 -0
- package/lib/src/instrumentation/xhr.js +88 -0
- package/lib/src/otel-compat.d.ts +67 -0
- package/lib/src/otel-compat.d.ts.map +1 -0
- package/lib/src/otel-compat.js +84 -0
- package/package.json +72 -0
- package/react-native.config.js +17 -0
- package/src/NativeDash0Mobile.ts +29 -0
- package/src/bridge/NativeBridge.ts +101 -0
- package/src/bridge/types.ts +188 -0
- package/src/index.ts +456 -0
- package/src/instrumentation/errors.ts +84 -0
- package/src/instrumentation/fetch.ts +93 -0
- package/src/instrumentation/navigation.ts +52 -0
- package/src/instrumentation/touch.ts +32 -0
- package/src/instrumentation/unhandledRejection.ts +75 -0
- package/src/instrumentation/xhr.ts +125 -0
- package/src/otel-compat.ts +159 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge payload contract between the JS layer and the native modules.
|
|
3
|
+
*
|
|
4
|
+
* This is the stable seam. Any change here is a cross-repo breaking change
|
|
5
|
+
* (JS package + Android module + iOS module must all move together).
|
|
6
|
+
*
|
|
7
|
+
* Values crossing the RN bridge must be JSON-serializable primitives.
|
|
8
|
+
* No Date, no Map, no Symbol, no functions.
|
|
9
|
+
*/
|
|
10
|
+
export type SeverityNumber = 1 | 5 | 9 | 13 | 17 | 21;
|
|
11
|
+
export type SpanKind = 'INTERNAL' | 'CLIENT' | 'SERVER' | 'PRODUCER' | 'CONSUMER';
|
|
12
|
+
export type SpanStatus = 'UNSET' | 'OK' | 'ERROR';
|
|
13
|
+
export type AttrValue = string | number | boolean | null;
|
|
14
|
+
export type Attributes = Record<string, AttrValue>;
|
|
15
|
+
/**
|
|
16
|
+
* Per-trigger flags for the native screenshot module. Field names and
|
|
17
|
+
* defaults mirror Android's `ScreenshotConfig` and iOS's `ScreenshotConfig`
|
|
18
|
+
* one-to-one. All fields optional; absent fields fall through to native
|
|
19
|
+
* defaults.
|
|
20
|
+
*/
|
|
21
|
+
export interface ScreenshotAutoCapture {
|
|
22
|
+
/** Master on/off. Required when passing an object form. Default `true` when present. */
|
|
23
|
+
enabled?: boolean;
|
|
24
|
+
/** Capture on every screen transition. Default `false` (high payload volume). */
|
|
25
|
+
captureOnScreenView?: boolean;
|
|
26
|
+
/** Capture when an uncaught exception occurs. Default `true`. */
|
|
27
|
+
captureOnError?: boolean;
|
|
28
|
+
/** Capture whenever a buffered-export policy fires (crash-recovery, ui-freeze, http-error). Default `true`. */
|
|
29
|
+
captureOnPolicyMatch?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Per-trigger flags for the native wireframe module. Field names and
|
|
33
|
+
* defaults mirror Android's `WireframeConfig` and iOS's `WireframeConfig`
|
|
34
|
+
* one-to-one. All fields optional; absent fields fall through to native
|
|
35
|
+
* defaults.
|
|
36
|
+
*/
|
|
37
|
+
export interface WireframeAutoCapture {
|
|
38
|
+
/** Master on/off. Required when passing an object form. Default `true` when present. */
|
|
39
|
+
enabled?: boolean;
|
|
40
|
+
/** Capture on every screen transition. Default `true` (primary journey trigger). */
|
|
41
|
+
captureOnScreenView?: boolean;
|
|
42
|
+
/** Capture on every tap. Default `false` — taps rarely change the view hierarchy; dedup absorbs the case anyway. */
|
|
43
|
+
captureOnTap?: boolean;
|
|
44
|
+
/** Capture when an uncaught exception occurs. Default `true`. */
|
|
45
|
+
captureOnError?: boolean;
|
|
46
|
+
/** Capture whenever a buffered-export policy fires. Default `true`. */
|
|
47
|
+
captureOnPolicyMatch?: boolean;
|
|
48
|
+
/** Emit `ui.wireframe.ref` with `mobile.wireframe.id` instead of the full JSON when content matches the last capture. Default `true`. */
|
|
49
|
+
dedupeByContentHash?: boolean;
|
|
50
|
+
}
|
|
51
|
+
export interface StartConfig {
|
|
52
|
+
serviceName: string;
|
|
53
|
+
serviceVersion?: string;
|
|
54
|
+
endpoint: string;
|
|
55
|
+
authToken?: string;
|
|
56
|
+
dataset?: string;
|
|
57
|
+
bufferConfig?: {
|
|
58
|
+
ramEvents?: number;
|
|
59
|
+
diskBytes?: number;
|
|
60
|
+
};
|
|
61
|
+
enablePolicyPolling?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Toggles for JS-side auto-instrumentation (fetch/XHR spans, JS error +
|
|
64
|
+
* unhandled rejection logs). Defaults to all-on. RN bridge uses the same
|
|
65
|
+
* flags to decide which native iOS/Android auto-capture suites to enable
|
|
66
|
+
* via `nativeAutoCapture`, so e.g. `autoCapture: { network: true }`
|
|
67
|
+
* enables the iOS `URLProtocol` swizzle in addition to the JS fetch/XHR
|
|
68
|
+
* wrappers. The default is OFF on the native side for `network`/`errors`
|
|
69
|
+
* — RN host apps typically don't want the native iOS URLProtocol swizzle
|
|
70
|
+
* or NSException handlers because they collide with RN's new-arch JS
|
|
71
|
+
* event loop (touch responder goes dormant).
|
|
72
|
+
*
|
|
73
|
+
* Lifecycle (`app.foreground` / `app.background` / `app.start`) is
|
|
74
|
+
* always-on via native instrumentation (Android ProcessLifecycleOwner,
|
|
75
|
+
* iOS NotificationCenter); there is no JS or per-flag knob for it.
|
|
76
|
+
*/
|
|
77
|
+
autoCapture?: {
|
|
78
|
+
network?: boolean;
|
|
79
|
+
errors?: boolean;
|
|
80
|
+
/** Native iOS/Android-only capability — captures UI taps via swizzle/recognizer. Off by default on RN. */
|
|
81
|
+
tap?: boolean;
|
|
82
|
+
/** Native-only — scroll spans. Off by default on RN. */
|
|
83
|
+
scroll?: boolean;
|
|
84
|
+
/** Native-only — text input spans. Off by default on RN. */
|
|
85
|
+
textInput?: boolean;
|
|
86
|
+
/** Native-only — SwiftUI/Fragment screen tracking. Off by default on RN (use react-navigation helper instead). */
|
|
87
|
+
screen?: boolean;
|
|
88
|
+
/** Native-only — main-thread freeze detection. Off by default on RN. */
|
|
89
|
+
freeze?: boolean;
|
|
90
|
+
/** Native-only — app.start + jank + memory gauges. Off by default on RN. */
|
|
91
|
+
vitals?: boolean;
|
|
92
|
+
/** Native-only — periodic device health gauges (battery/thermal/network). Off by default on RN. */
|
|
93
|
+
deviceStats?: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Native-only — screenshot capture at journey boundaries + errors. Off
|
|
96
|
+
* by default on RN. Accepts either a bare boolean (`true` enables with
|
|
97
|
+
* native defaults) or an options object exposing the per-trigger flags
|
|
98
|
+
* mirrored from Android's `ScreenshotConfig` and iOS's `ScreenshotConfig`.
|
|
99
|
+
*
|
|
100
|
+
* NOTE: the per-trigger fields are forward-compatible. Today's bridge
|
|
101
|
+
* carries only the `enabled` bit through to native. Passing an object
|
|
102
|
+
* is equivalent to passing `true` until the bridge grows per-module
|
|
103
|
+
* options in a follow-up. Set the trigger flags natively (Android: in
|
|
104
|
+
* `MobileConfig.screenshotConfig`, iOS: in `MobileConfig`'s
|
|
105
|
+
* `screenshotConfig` param) until then.
|
|
106
|
+
*/
|
|
107
|
+
screenshot?: boolean | ScreenshotAutoCapture;
|
|
108
|
+
/**
|
|
109
|
+
* Native-only — wireframe capture at screen transitions, optional taps,
|
|
110
|
+
* errors, and policy matches. Same shape rules as `screenshot` above.
|
|
111
|
+
*/
|
|
112
|
+
wireframe?: boolean | WireframeAutoCapture;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Extra resource attributes merged into the native SDK's resource. Used by
|
|
116
|
+
* the RN bridge to inject `telemetry.distro.name` / `telemetry.distro.version`
|
|
117
|
+
* so Dash0 knows the telemetry came through the React Native distribution
|
|
118
|
+
* rather than direct Kotlin/Swift SDK usage. Caller-overridable for apps
|
|
119
|
+
* that want to add their own build/deployment tags.
|
|
120
|
+
*/
|
|
121
|
+
extraResourceAttributes?: Record<string, string>;
|
|
122
|
+
}
|
|
123
|
+
export interface LogPayload {
|
|
124
|
+
kind: 'log';
|
|
125
|
+
name: string;
|
|
126
|
+
severity: SeverityNumber;
|
|
127
|
+
attributes: Attributes;
|
|
128
|
+
timeUnixNano: string;
|
|
129
|
+
}
|
|
130
|
+
export interface SpanStartPayload {
|
|
131
|
+
kind: 'spanStart';
|
|
132
|
+
spanId: string;
|
|
133
|
+
parentSpanId?: string;
|
|
134
|
+
name: string;
|
|
135
|
+
spanKind: SpanKind;
|
|
136
|
+
attributes: Attributes;
|
|
137
|
+
startTimeUnixNano: string;
|
|
138
|
+
}
|
|
139
|
+
export interface SpanEndPayload {
|
|
140
|
+
kind: 'spanEnd';
|
|
141
|
+
spanId: string;
|
|
142
|
+
status: SpanStatus;
|
|
143
|
+
statusMessage?: string;
|
|
144
|
+
attributes: Attributes;
|
|
145
|
+
endTimeUnixNano: string;
|
|
146
|
+
}
|
|
147
|
+
export interface MetricPayload {
|
|
148
|
+
kind: 'metric';
|
|
149
|
+
name: string;
|
|
150
|
+
instrumentType: 'counter' | 'histogram' | 'gauge';
|
|
151
|
+
value: number;
|
|
152
|
+
attributes: Attributes;
|
|
153
|
+
timeUnixNano: string;
|
|
154
|
+
}
|
|
155
|
+
export type BridgePayload = LogPayload | SpanStartPayload | SpanEndPayload | MetricPayload;
|
|
156
|
+
export interface NativeDash0MobileModule {
|
|
157
|
+
start(config: StartConfig): Promise<void>;
|
|
158
|
+
emitBatch(payloads: BridgePayload[]): Promise<void>;
|
|
159
|
+
flushWindow(minutes: number): Promise<void>;
|
|
160
|
+
shutdown(): Promise<void>;
|
|
161
|
+
startJourney(name: string): Promise<string>;
|
|
162
|
+
endJourney(journeyId: string): Promise<void>;
|
|
163
|
+
captureScreenshot(trigger: string): Promise<void>;
|
|
164
|
+
captureWireframe(trigger: string): Promise<void>;
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/bridge/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge payload contract between the JS layer and the native modules.
|
|
3
|
+
*
|
|
4
|
+
* This is the stable seam. Any change here is a cross-repo breaking change
|
|
5
|
+
* (JS package + Android module + iOS module must all move together).
|
|
6
|
+
*
|
|
7
|
+
* Values crossing the RN bridge must be JSON-serializable primitives.
|
|
8
|
+
* No Date, no Map, no Symbol, no functions.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @dash0/mobile-react-native — public API
|
|
3
|
+
*
|
|
4
|
+
* Thin JS facade. All buffering, policy, OTLP export, crash recovery happen
|
|
5
|
+
* in the native modules. This file only marshals calls onto a NativeBridge.
|
|
6
|
+
*
|
|
7
|
+
* See docs/epics/REACT_NATIVE_EPIC.md.
|
|
8
|
+
*/
|
|
9
|
+
import type { Attributes, NativeDash0MobileModule, SeverityNumber, SpanKind, StartConfig } from './bridge/types';
|
|
10
|
+
export type { Attributes, SeverityNumber, StartConfig } from './bridge/types';
|
|
11
|
+
export { installReactNavigationInstrumentation } from './instrumentation/navigation';
|
|
12
|
+
export { withTapTelemetry } from './instrumentation/touch';
|
|
13
|
+
export { otel } from './otel-compat';
|
|
14
|
+
export interface SpanHandle {
|
|
15
|
+
setAttribute(key: string, value: string | number | boolean): void;
|
|
16
|
+
setStatus(status: 'OK' | 'ERROR', message?: string): void;
|
|
17
|
+
end(): void;
|
|
18
|
+
}
|
|
19
|
+
export declare function __resetTimestampAnchorForTests__(): void;
|
|
20
|
+
export declare const Dash0Mobile: {
|
|
21
|
+
start(config: StartConfig): Promise<void>;
|
|
22
|
+
log(name: string, attributes?: Attributes, severity?: SeverityNumber): void;
|
|
23
|
+
startSpan(name: string, attributes?: Attributes, spanKind?: SpanKind): SpanHandle;
|
|
24
|
+
span<T>(name: string, fn: (handle: SpanHandle) => Promise<T> | T, attributes?: Attributes): Promise<T>;
|
|
25
|
+
recordMetric(name: string, value: number, instrumentType?: "counter" | "histogram" | "gauge", attributes?: Attributes): void;
|
|
26
|
+
flushWindow(minutes: number): Promise<void>;
|
|
27
|
+
startJourney(name: string): Promise<string | null>;
|
|
28
|
+
endJourney(journeyId: string): Promise<void>;
|
|
29
|
+
captureScreenshot(trigger?: string): Promise<void>;
|
|
30
|
+
captureWireframe(trigger?: string): Promise<void>;
|
|
31
|
+
shutdown(): Promise<void>;
|
|
32
|
+
};
|
|
33
|
+
export declare function __setNativeForTesting(native: NativeDash0MobileModule | null): void;
|
|
34
|
+
export declare function __resetForTesting(): void;
|
|
35
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EACV,UAAU,EAEV,uBAAuB,EACvB,cAAc,EACd,QAAQ,EAER,WAAW,EACZ,MAAM,gBAAgB,CAAC;AAExB,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EAAE,qCAAqC,EAAE,MAAM,8BAA8B,CAAC;AACrF,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC,MAAM,WAAW,UAAU;IACzB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IAClE,SAAS,CAAC,MAAM,EAAE,IAAI,GAAG,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1D,GAAG,IAAI,IAAI,CAAC;CACb;AA8ED,wBAAgB,gCAAgC,IAAI,IAAI,CAGvD;AAyCD,eAAO,MAAM,WAAW;kBACF,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;cA6DrC,MAAM,eAAc,UAAU,aAAiB,cAAc,GAAO,IAAI;oBAwBlE,MAAM,eAAc,UAAU,aAAiB,QAAQ,GAAgB,UAAU;SAsDtF,CAAC,QACJ,MAAM,MACR,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,eAC7B,UAAU,GACtB,OAAO,CAAC,CAAC,CAAC;uBAeL,MAAM,SACL,MAAM,mBACG,SAAS,GAAG,WAAW,GAAG,OAAO,eACrC,UAAU,GACrB,IAAI;yBAYoB,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;uBAYxB,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;0BAO5B,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;gCAOjB,MAAM,GAAc,OAAO,CAAC,IAAI,CAAC;+BAOlC,MAAM,GAAc,OAAO,CAAC,IAAI,CAAC;gBAO/C,OAAO,CAAC,IAAI,CAAC;CAgBhC,CAAC;AA4DF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,uBAAuB,GAAG,IAAI,GAAG,IAAI,CAElF;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAKxC"}
|
package/lib/src/index.js
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @dash0/mobile-react-native — public API
|
|
3
|
+
*
|
|
4
|
+
* Thin JS facade. All buffering, policy, OTLP export, crash recovery happen
|
|
5
|
+
* in the native modules. This file only marshals calls onto a NativeBridge.
|
|
6
|
+
*
|
|
7
|
+
* See docs/epics/REACT_NATIVE_EPIC.md.
|
|
8
|
+
*/
|
|
9
|
+
import { NativeBridge } from './bridge/NativeBridge';
|
|
10
|
+
import { installFetchInstrumentation } from './instrumentation/fetch';
|
|
11
|
+
import { installXhrInstrumentation } from './instrumentation/xhr';
|
|
12
|
+
import { installErrorInstrumentation } from './instrumentation/errors';
|
|
13
|
+
import { installUnhandledRejectionInstrumentation } from './instrumentation/unhandledRejection';
|
|
14
|
+
export { installReactNavigationInstrumentation } from './instrumentation/navigation';
|
|
15
|
+
export { withTapTelemetry } from './instrumentation/touch';
|
|
16
|
+
export { otel } from './otel-compat';
|
|
17
|
+
let injectedNative = null;
|
|
18
|
+
let bridge = null;
|
|
19
|
+
let started = false;
|
|
20
|
+
let autoInstrUninstallers = [];
|
|
21
|
+
// Ambient parent-span stack. Each `startSpan` pushes the new spanId; each
|
|
22
|
+
// `.end()` pops. Children emitted while an outer span is live pick up the
|
|
23
|
+
// top of the stack as their `parentSpanId`, so Dash0 renders proper
|
|
24
|
+
// waterfalls without callers threading parent handles manually.
|
|
25
|
+
//
|
|
26
|
+
// Limitation: a single global stack is only correct for strictly-nested
|
|
27
|
+
// (LIFO) span work — sufficient for `Dash0Mobile.span('x', async () => { ... })`
|
|
28
|
+
// and sequential `startSpan/.end()` pairs. Concurrent async span trees would
|
|
29
|
+
// need AsyncLocalStorage / Hermes async-hooks; RN doesn't expose those
|
|
30
|
+
// cleanly, so we accept the limitation and document it on the public API.
|
|
31
|
+
const spanStack = [];
|
|
32
|
+
/// Anchor pair used by `nowUnixNano` to combine an absolute-epoch
|
|
33
|
+
/// base (from `Date.now()`) with a sub-ms monotonic delta (from
|
|
34
|
+
/// `performance.now()`). Initialized lazily on the first timestamp
|
|
35
|
+
/// request so we don't pay the cost for consumers who never emit.
|
|
36
|
+
///
|
|
37
|
+
/// Why the anchor pattern: `performance.now()` gives sub-ms precision
|
|
38
|
+
/// but returns time-since-an-arbitrary-origin, not wall-clock epoch.
|
|
39
|
+
/// `Date.now()` gives wall-clock but only ms resolution. Capturing
|
|
40
|
+
/// both once, then deriving each timestamp as `epochAnchor + perfDelta`
|
|
41
|
+
/// yields sub-ms wall-clock nanoseconds without per-call drift.
|
|
42
|
+
let _epochAnchorMs = null;
|
|
43
|
+
let _perfAnchorMs = null;
|
|
44
|
+
function perfNow() {
|
|
45
|
+
// RN 0.85 new-arch ships `global.performance.now` via JSI. Older
|
|
46
|
+
// runtimes (Hermes < 0.12, some test harnesses) may not. Fall back
|
|
47
|
+
// gracefully — callers still get correct ms-resolution nanoseconds
|
|
48
|
+
// in that case, just not the sub-ms fidelity.
|
|
49
|
+
const g = globalThis;
|
|
50
|
+
if (typeof g.performance?.now === 'function') {
|
|
51
|
+
return g.performance.now();
|
|
52
|
+
}
|
|
53
|
+
return Date.now();
|
|
54
|
+
}
|
|
55
|
+
function nowUnixNano() {
|
|
56
|
+
// Initialize anchor lazily. The offset between the two reads is at
|
|
57
|
+
// most one JS tick — negligible for observability purposes.
|
|
58
|
+
if (_epochAnchorMs === null || _perfAnchorMs === null) {
|
|
59
|
+
_epochAnchorMs = Date.now();
|
|
60
|
+
_perfAnchorMs = perfNow();
|
|
61
|
+
}
|
|
62
|
+
const elapsedMs = perfNow() - _perfAnchorMs;
|
|
63
|
+
// Sub-ms fraction; convert to nanoseconds as an integer.
|
|
64
|
+
const extraNanos = Math.floor(elapsedMs * 1000000);
|
|
65
|
+
// BigInt is required for correctness. Unix nanoseconds in 2026
|
|
66
|
+
// are ~1.77e18, well past JS `Number.MAX_SAFE_INTEGER` (2^53 ≈
|
|
67
|
+
// 9.01e15). The previous `Date.now() * 1_000_000` implementation
|
|
68
|
+
// lost precision on every call (rounding to ~128-nanosecond bands),
|
|
69
|
+
// which collapsed nested child spans' start+end timestamps to
|
|
70
|
+
// identical values and surfaced as `duration=0ms` in Dash0.
|
|
71
|
+
const totalNanos = BigInt(_epochAnchorMs) * 1000000n + BigInt(extraNanos);
|
|
72
|
+
return totalNanos.toString();
|
|
73
|
+
}
|
|
74
|
+
/// Test-only: reset the anchors so each test case starts fresh.
|
|
75
|
+
/// Not exported from the public entry point — accessed in Jest via
|
|
76
|
+
/// the internal require path.
|
|
77
|
+
export function __resetTimestampAnchorForTests__() {
|
|
78
|
+
_epochAnchorMs = null;
|
|
79
|
+
_perfAnchorMs = null;
|
|
80
|
+
}
|
|
81
|
+
function randomSpanId() {
|
|
82
|
+
let id = '';
|
|
83
|
+
for (let i = 0; i < 16; i++) {
|
|
84
|
+
id += Math.floor(Math.random() * 16).toString(16);
|
|
85
|
+
}
|
|
86
|
+
return id;
|
|
87
|
+
}
|
|
88
|
+
function resolveNative() {
|
|
89
|
+
if (injectedNative)
|
|
90
|
+
return injectedNative;
|
|
91
|
+
// Production path: lazily require react-native to avoid pulling it into
|
|
92
|
+
// tests that stub at the unit level. If RN isn't present we return null
|
|
93
|
+
// and pre-start calls become no-ops.
|
|
94
|
+
try {
|
|
95
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
96
|
+
const rn = require('react-native');
|
|
97
|
+
return rn?.NativeModules?.Dash0Mobile ?? null;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Version of this RN bridge package. Sent as `telemetry.distro.version` so
|
|
104
|
+
// Dash0 can distinguish RN-originated telemetry from direct native SDK
|
|
105
|
+
// callers and correlate issues to a specific bridge release. Keep this in
|
|
106
|
+
// sync with package.json on each release.
|
|
107
|
+
const DISTRO_NAME = 'dash0-react-native';
|
|
108
|
+
const DISTRO_VERSION = '0.1.0-alpha';
|
|
109
|
+
function resolveReactNativeVersion() {
|
|
110
|
+
try {
|
|
111
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
112
|
+
const rn = require('react-native/package.json');
|
|
113
|
+
return typeof rn?.version === 'string' ? rn.version : undefined;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
export const Dash0Mobile = {
|
|
120
|
+
async start(config) {
|
|
121
|
+
const native = resolveNative();
|
|
122
|
+
if (!native) {
|
|
123
|
+
// No native module wired. Stay quiet — caller is likely in a non-RN
|
|
124
|
+
// environment (Jest, SSR) and shouldn't crash.
|
|
125
|
+
started = false;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
bridge = new NativeBridge(native);
|
|
129
|
+
// Inject RN distribution attributes as OTel-spec resource attributes.
|
|
130
|
+
// Caller-provided extras win (lets apps override for testing/tagging).
|
|
131
|
+
const rnVersion = resolveReactNativeVersion();
|
|
132
|
+
// Translate the `autoCapture` flags into a native-capability token list the
|
|
133
|
+
// bridge (iOS/Android) can use to build `AutoCaptureOptions`. Native
|
|
134
|
+
// default is OFF for everything — callers must explicitly set a flag to
|
|
135
|
+
// `true` to enable the native suite on that capability. This avoids the
|
|
136
|
+
// iOS URLProtocol / NSException / signal-handler swizzles by default,
|
|
137
|
+
// which collide with RN's new-arch JS event loop.
|
|
138
|
+
const nativeAutoCapture = buildNativeAutoCaptureTokens(config.autoCapture);
|
|
139
|
+
const mergedConfig = {
|
|
140
|
+
...config,
|
|
141
|
+
extraResourceAttributes: {
|
|
142
|
+
'telemetry.distro.name': DISTRO_NAME,
|
|
143
|
+
'telemetry.distro.version': DISTRO_VERSION,
|
|
144
|
+
...(rnVersion ? { 'app.framework': 'react-native', 'app.framework.version': rnVersion } : {}),
|
|
145
|
+
...(config.extraResourceAttributes ?? {}),
|
|
146
|
+
},
|
|
147
|
+
nativeAutoCapture,
|
|
148
|
+
};
|
|
149
|
+
await native.start(mergedConfig);
|
|
150
|
+
started = true;
|
|
151
|
+
const auto = config.autoCapture ?? {};
|
|
152
|
+
if (auto.network !== false) {
|
|
153
|
+
const collectorHost = hostFromEndpoint(config.endpoint);
|
|
154
|
+
const ignoredHosts = collectorHost ? [collectorHost] : [];
|
|
155
|
+
// On React Native, `fetch` is implemented on top of XHR, so every
|
|
156
|
+
// fetch() call also fires the XHR instrumentation — installing both
|
|
157
|
+
// produces two spans per request. XHR is the authoritative layer
|
|
158
|
+
// because it also captures direct XHR callers (axios, Apollo HTTP
|
|
159
|
+
// link, legacy code) that don't go through fetch. In non-RN JS
|
|
160
|
+
// environments (future web/SSR use) fetch is native and independent
|
|
161
|
+
// of XHR, so both shims are needed.
|
|
162
|
+
if (!isReactNative()) {
|
|
163
|
+
autoInstrUninstallers.push(installFetchInstrumentation({ ignoredHosts }));
|
|
164
|
+
}
|
|
165
|
+
autoInstrUninstallers.push(installXhrInstrumentation({ ignoredHosts }));
|
|
166
|
+
}
|
|
167
|
+
if (auto.errors !== false) {
|
|
168
|
+
autoInstrUninstallers.push(installErrorInstrumentation());
|
|
169
|
+
autoInstrUninstallers.push(installUnhandledRejectionInstrumentation());
|
|
170
|
+
}
|
|
171
|
+
// Lifecycle (`app.foreground` / `app.background` / `app.start`) is now
|
|
172
|
+
// emitted by native instrumentation on both platforms — Android via
|
|
173
|
+
// ProcessLifecycleOwner, iOS via NotificationCenter with applicationState
|
|
174
|
+
// late-init synthesis. The previous JS-side AppState shim was deleted
|
|
175
|
+
// because RN 0.85 new-arch made it unreliable (TurboModule init race).
|
|
176
|
+
},
|
|
177
|
+
log(name, attributes = {}, severity = 9) {
|
|
178
|
+
if (!started || !bridge)
|
|
179
|
+
return;
|
|
180
|
+
const payload = {
|
|
181
|
+
kind: 'log',
|
|
182
|
+
name,
|
|
183
|
+
severity,
|
|
184
|
+
attributes,
|
|
185
|
+
timeUnixNano: nowUnixNano(),
|
|
186
|
+
};
|
|
187
|
+
// FATAL-severity logs bypass the 50ms debounce via `emitSync`, which
|
|
188
|
+
// calls `native.emitBatch` without any `await`. The payload crosses
|
|
189
|
+
// the RN bridge in the current stack frame — critical on crash paths
|
|
190
|
+
// because any microtask boundary (including the `await` inside
|
|
191
|
+
// `flush()`) loses the race against the handler's continuation into
|
|
192
|
+
// RN's fatal reporter. Once the payload is on the native side, the
|
|
193
|
+
// iOS SDK's willTerminate auto-forceFlush (commit 1a69c7e) persists
|
|
194
|
+
// it to disk and attempts OTLP export before process exit.
|
|
195
|
+
if (severity >= 21) {
|
|
196
|
+
bridge.emitSync(payload);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
bridge.emit(payload);
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
startSpan(name, attributes = {}, spanKind = 'INTERNAL') {
|
|
203
|
+
const spanId = randomSpanId();
|
|
204
|
+
const parentSpanId = spanStack.length > 0 ? spanStack[spanStack.length - 1] : undefined;
|
|
205
|
+
const startPayload = {
|
|
206
|
+
kind: 'spanStart',
|
|
207
|
+
spanId,
|
|
208
|
+
parentSpanId,
|
|
209
|
+
name,
|
|
210
|
+
spanKind,
|
|
211
|
+
attributes: { ...attributes },
|
|
212
|
+
startTimeUnixNano: nowUnixNano(),
|
|
213
|
+
};
|
|
214
|
+
const active = {
|
|
215
|
+
payload: startPayload,
|
|
216
|
+
attrs: {},
|
|
217
|
+
status: 'UNSET',
|
|
218
|
+
ended: false,
|
|
219
|
+
};
|
|
220
|
+
spanStack.push(spanId);
|
|
221
|
+
if (started && bridge) {
|
|
222
|
+
bridge.emit(startPayload);
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
setAttribute(key, value) {
|
|
226
|
+
active.attrs[key] = value;
|
|
227
|
+
},
|
|
228
|
+
setStatus(status, message) {
|
|
229
|
+
active.status = status;
|
|
230
|
+
active.statusMessage = message;
|
|
231
|
+
},
|
|
232
|
+
end() {
|
|
233
|
+
if (active.ended)
|
|
234
|
+
return;
|
|
235
|
+
active.ended = true;
|
|
236
|
+
// Pop this spanId off the stack. Tolerate out-of-order ends by
|
|
237
|
+
// scanning from the top — the common case is LIFO, but mis-ordered
|
|
238
|
+
// ends (e.g. a long-lived outer span that ends after an inner
|
|
239
|
+
// one that was itself popped correctly) shouldn't corrupt the stack.
|
|
240
|
+
const idx = spanStack.lastIndexOf(spanId);
|
|
241
|
+
if (idx >= 0)
|
|
242
|
+
spanStack.splice(idx, 1);
|
|
243
|
+
if (!started || !bridge)
|
|
244
|
+
return;
|
|
245
|
+
bridge.emit({
|
|
246
|
+
kind: 'spanEnd',
|
|
247
|
+
spanId,
|
|
248
|
+
status: active.status === 'UNSET' ? 'OK' : active.status,
|
|
249
|
+
statusMessage: active.statusMessage,
|
|
250
|
+
attributes: active.attrs,
|
|
251
|
+
endTimeUnixNano: nowUnixNano(),
|
|
252
|
+
});
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
},
|
|
256
|
+
async span(name, fn, attributes) {
|
|
257
|
+
const handle = this.startSpan(name, attributes);
|
|
258
|
+
try {
|
|
259
|
+
const result = await fn(handle);
|
|
260
|
+
handle.setStatus('OK');
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
handle.setStatus('ERROR', err instanceof Error ? err.message : String(err));
|
|
265
|
+
throw err;
|
|
266
|
+
}
|
|
267
|
+
finally {
|
|
268
|
+
handle.end();
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
recordMetric(name, value, instrumentType = 'counter', attributes = {}) {
|
|
272
|
+
if (!started || !bridge)
|
|
273
|
+
return;
|
|
274
|
+
bridge.emit({
|
|
275
|
+
kind: 'metric',
|
|
276
|
+
name,
|
|
277
|
+
instrumentType,
|
|
278
|
+
value,
|
|
279
|
+
attributes,
|
|
280
|
+
timeUnixNano: nowUnixNano(),
|
|
281
|
+
});
|
|
282
|
+
},
|
|
283
|
+
async flushWindow(minutes) {
|
|
284
|
+
if (!started || !bridge)
|
|
285
|
+
return;
|
|
286
|
+
await bridge.flush();
|
|
287
|
+
const native = resolveNative();
|
|
288
|
+
if (native)
|
|
289
|
+
await native.flushWindow(minutes);
|
|
290
|
+
},
|
|
291
|
+
// ─── User Journey API (UJ-020) ──────────────────────────────────────────
|
|
292
|
+
// Thin passthrough to native `OTelMobile.startJourney/endJourney`. The
|
|
293
|
+
// native side creates the journey span and triggers screenshot + wireframe
|
|
294
|
+
// captures at start/end boundaries.
|
|
295
|
+
async startJourney(name) {
|
|
296
|
+
if (!started)
|
|
297
|
+
return null;
|
|
298
|
+
const native = resolveNative();
|
|
299
|
+
if (!native)
|
|
300
|
+
return null;
|
|
301
|
+
return native.startJourney(name);
|
|
302
|
+
},
|
|
303
|
+
async endJourney(journeyId) {
|
|
304
|
+
if (!started)
|
|
305
|
+
return;
|
|
306
|
+
const native = resolveNative();
|
|
307
|
+
if (!native)
|
|
308
|
+
return;
|
|
309
|
+
await native.endJourney(journeyId);
|
|
310
|
+
},
|
|
311
|
+
async captureScreenshot(trigger = 'manual') {
|
|
312
|
+
if (!started)
|
|
313
|
+
return;
|
|
314
|
+
const native = resolveNative();
|
|
315
|
+
if (!native)
|
|
316
|
+
return;
|
|
317
|
+
await native.captureScreenshot(trigger);
|
|
318
|
+
},
|
|
319
|
+
async captureWireframe(trigger = 'manual') {
|
|
320
|
+
if (!started)
|
|
321
|
+
return;
|
|
322
|
+
const native = resolveNative();
|
|
323
|
+
if (!native)
|
|
324
|
+
return;
|
|
325
|
+
await native.captureWireframe(trigger);
|
|
326
|
+
},
|
|
327
|
+
async shutdown() {
|
|
328
|
+
for (const uninstall of autoInstrUninstallers) {
|
|
329
|
+
try {
|
|
330
|
+
uninstall();
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
// Swallow — shutdown should never throw.
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
autoInstrUninstallers = [];
|
|
337
|
+
if (!bridge)
|
|
338
|
+
return;
|
|
339
|
+
await bridge.flush();
|
|
340
|
+
const native = resolveNative();
|
|
341
|
+
if (native)
|
|
342
|
+
await native.shutdown();
|
|
343
|
+
started = false;
|
|
344
|
+
bridge = null;
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
const ENDPOINT_HOST_RE = /^[a-z][a-z0-9+.-]*:\/\/([^/:?#]+)/i;
|
|
348
|
+
function hostFromEndpoint(endpoint) {
|
|
349
|
+
const match = ENDPOINT_HOST_RE.exec(endpoint);
|
|
350
|
+
return match ? match[1].toLowerCase() : null;
|
|
351
|
+
}
|
|
352
|
+
// React Native sets `navigator.product === 'ReactNative'` — the standard
|
|
353
|
+
// public signal for code that wants to differ by JS environment. This is
|
|
354
|
+
// the same check used by Sentry, Datadog, and RN itself internally. Jest
|
|
355
|
+
// / Node / SSR won't match, so test and non-RN environments still
|
|
356
|
+
// exercise both network shims.
|
|
357
|
+
function isReactNative() {
|
|
358
|
+
const nav = globalThis.navigator;
|
|
359
|
+
return nav?.product === 'ReactNative';
|
|
360
|
+
}
|
|
361
|
+
const NATIVE_AUTO_CAPTURE_FLAGS = [
|
|
362
|
+
['network', 'network'],
|
|
363
|
+
['errors', 'errors'],
|
|
364
|
+
['tap', 'tap'],
|
|
365
|
+
['scroll', 'scroll'],
|
|
366
|
+
['textInput', 'textInput'],
|
|
367
|
+
['screen', 'screen'],
|
|
368
|
+
['freeze', 'freeze'],
|
|
369
|
+
['vitals', 'vitals'],
|
|
370
|
+
['deviceStats', 'deviceStats'],
|
|
371
|
+
['screenshot', 'screenshot'],
|
|
372
|
+
['wireframe', 'wireframe'],
|
|
373
|
+
];
|
|
374
|
+
function buildNativeAutoCaptureTokens(ac) {
|
|
375
|
+
if (!ac)
|
|
376
|
+
return [];
|
|
377
|
+
const out = [];
|
|
378
|
+
for (const [flag, token] of NATIVE_AUTO_CAPTURE_FLAGS) {
|
|
379
|
+
const value = ac[flag];
|
|
380
|
+
// Object form (screenshot / wireframe per-trigger): treat as enabled
|
|
381
|
+
// unless `enabled` is explicitly `false`. The bridge protocol carries
|
|
382
|
+
// only this on/off bit today — per-trigger fields are forward-compatible
|
|
383
|
+
// and currently must be configured natively. See ScreenshotAutoCapture /
|
|
384
|
+
// WireframeAutoCapture doc comments in bridge/types.ts.
|
|
385
|
+
if (value === true) {
|
|
386
|
+
out.push(token);
|
|
387
|
+
}
|
|
388
|
+
else if (typeof value === 'object' && value !== null) {
|
|
389
|
+
const enabled = value.enabled;
|
|
390
|
+
if (enabled !== false)
|
|
391
|
+
out.push(token);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return out;
|
|
395
|
+
}
|
|
396
|
+
// ─── test hooks ──────────────────────────────────────────────────────────────
|
|
397
|
+
// These are intentionally exported but prefixed __ to signal "not for app use."
|
|
398
|
+
// Native-module tests inject a mock here; a CI lint rule should ban callers
|
|
399
|
+
// outside __tests__/ from importing them.
|
|
400
|
+
export function __setNativeForTesting(native) {
|
|
401
|
+
injectedNative = native;
|
|
402
|
+
}
|
|
403
|
+
export function __resetForTesting() {
|
|
404
|
+
injectedNative = null;
|
|
405
|
+
bridge = null;
|
|
406
|
+
started = false;
|
|
407
|
+
spanStack.length = 0;
|
|
408
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error auto-instrumentation for React Native.
|
|
3
|
+
*
|
|
4
|
+
* Chains our handler into RN's `ErrorUtils.setGlobalHandler`, emits an
|
|
5
|
+
* `app.error` log with OTel `exception.*` semconv attributes, then delegates
|
|
6
|
+
* to the previous handler so Sentry/Bugsnag/redbox keep working.
|
|
7
|
+
*
|
|
8
|
+
* Deduplication: identical (type + message) errors inside a 5-minute window
|
|
9
|
+
* collapse to a single log, matching the Android ErrorInstrumentation
|
|
10
|
+
* policy (loops that throw on every frame shouldn't DoS the collector).
|
|
11
|
+
*/
|
|
12
|
+
export declare const DEDUPE_WINDOW_MS: number;
|
|
13
|
+
export declare function installErrorInstrumentation(): () => void;
|
|
14
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/instrumentation/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,eAAO,MAAM,gBAAgB,QAAgB,CAAC;AAsB9C,wBAAgB,2BAA2B,IAAI,MAAM,IAAI,CA+CxD"}
|