@goliapkg/sentori-react-native 5.0.0 → 5.1.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/MIGRATION.md +131 -0
- package/README.md +40 -30
- package/android/src/main/java/com/sentori/SentoriModule.kt +8 -0
- package/android/src/main/java/com/sentori/SentoriScreenshotCapture.kt +36 -4
- package/ios/SentoriCrashHandler.swift +48 -4
- package/ios/SentoriModule.swift +12 -0
- package/ios/SentoriScreenshotCapture.swift +34 -3
- package/lib/config.d.ts +1 -0
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -0
- package/lib/index.js.map +1 -1
- package/lib/init.d.ts.map +1 -1
- package/lib/init.js +18 -6
- package/lib/init.js.map +1 -1
- package/lib/mask.d.ts +8 -0
- package/lib/mask.d.ts.map +1 -0
- package/lib/mask.js +35 -0
- package/lib/mask.js.map +1 -0
- package/lib/native.d.ts +4 -11
- package/lib/native.d.ts.map +1 -1
- package/lib/native.js +23 -0
- package/lib/native.js.map +1 -1
- package/lib/rage-tap.d.ts.map +1 -1
- package/lib/rage-tap.js +30 -2
- package/lib/rage-tap.js.map +1 -1
- package/lib/replay-screens.d.ts +15 -0
- package/lib/replay-screens.d.ts.map +1 -0
- package/lib/replay-screens.js +87 -0
- package/lib/replay-screens.js.map +1 -0
- package/lib/responsiveness-detector.d.ts +18 -0
- package/lib/responsiveness-detector.d.ts.map +1 -0
- package/lib/responsiveness-detector.js +55 -0
- package/lib/responsiveness-detector.js.map +1 -0
- package/package.json +3 -2
- package/src/__tests__/replay-screens.test.ts +63 -0
- package/src/__tests__/responsiveness-detector.test.ts +74 -0
- package/src/config.ts +1 -0
- package/src/index.ts +3 -0
- package/src/init.ts +27 -9
- package/src/mask.ts +39 -0
- package/src/native.ts +33 -0
- package/src/rage-tap.tsx +46 -2
- package/src/replay-screens.ts +95 -0
- package/src/responsiveness-detector.ts +66 -0
package/MIGRATION.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Migrating to @goliapkg/sentori-react-native 5.x
|
|
2
|
+
|
|
3
|
+
5.0 is the five-kind redesign: the SDK surface shrinks from 40+
|
|
4
|
+
verbs to **eight**, everything is synchronous and can never throw
|
|
5
|
+
into your app, and the server it talks to is the self-hosted
|
|
6
|
+
Sentori 2.x stack. This guide covers every 4.x API a host app is
|
|
7
|
+
likely to use, in the order a migration usually meets them.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
sentori.init(config) sentori.user(u) sentori.context(patch)
|
|
11
|
+
sentori.error(err) sentori.warn(name) sentori.trace(name)
|
|
12
|
+
sentori.assert(n, ok) sentori.probe(ref)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 1. Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bun add @goliapkg/sentori-react-native@^5.0.0
|
|
19
|
+
# Expo apps also bump the config plugin:
|
|
20
|
+
bun add @goliapkg/sentori-expo@^10.0.0
|
|
21
|
+
# Build pipelines:
|
|
22
|
+
bun add -d @goliapkg/sentori-cli@^1.0.0
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`@goliapkg/sentori-core` 2.x comes in transitively. The native
|
|
26
|
+
layer (iOS/Android) is unchanged — no pod/gradle churn, an
|
|
27
|
+
existing dev client keeps working.
|
|
28
|
+
|
|
29
|
+
## 2. Tokens
|
|
30
|
+
|
|
31
|
+
4.x `st_pk_…` tokens do not work against the 2.x server. Mint a
|
|
32
|
+
fresh **ingest**-scope token in **Settings → Tokens** and swap it
|
|
33
|
+
into your config. Build-time tooling (sourcemap upload, probe
|
|
34
|
+
sync) needs a separate **api**-scope token — the ingest token
|
|
35
|
+
shipped inside the app is deliberately refused there.
|
|
36
|
+
|
|
37
|
+
## 3. `init`
|
|
38
|
+
|
|
39
|
+
The `capture.*` switch block is gone. Warn scenarios are detected,
|
|
40
|
+
not configured; replay is a single rolling-buffer knob.
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
// 4.x
|
|
44
|
+
init({
|
|
45
|
+
capture: {
|
|
46
|
+
longTaskMonitor: true,
|
|
47
|
+
preCrashSentinel: true,
|
|
48
|
+
replay: 'wireframe',
|
|
49
|
+
sampleProfiler: false,
|
|
50
|
+
screenshot: true,
|
|
51
|
+
sessionTrail: true,
|
|
52
|
+
},
|
|
53
|
+
environment: 'prod',
|
|
54
|
+
ingestUrl, token, release,
|
|
55
|
+
onReady: (info) => { ... },
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// 5.x
|
|
59
|
+
init({
|
|
60
|
+
detect: {
|
|
61
|
+
rageTap: true,
|
|
62
|
+
longFreeze: true,
|
|
63
|
+
slowColdStart: true,
|
|
64
|
+
// slowApi stays opt-in
|
|
65
|
+
},
|
|
66
|
+
replaySeconds: 15, // 0 disables; ships only on error/warn
|
|
67
|
+
environment: 'prod',
|
|
68
|
+
ingestUrl, token, release,
|
|
69
|
+
logLevel: 'warn',
|
|
70
|
+
})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Removed with no replacement: `onReady`, `sampleProfiler`,
|
|
74
|
+
`sessionTrail` (the signal ring is always on), `launchCrashGuard`.
|
|
75
|
+
Cold-start timing is internal to the `slow_cold_start` detector —
|
|
76
|
+
`getColdStartMs()` no longer exists; drop it from log lines.
|
|
77
|
+
|
|
78
|
+
## 4. Verb mapping
|
|
79
|
+
|
|
80
|
+
| 4.x | 5.x |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `sentori.captureException(err, { tags })` | `sentori.error(err, tags)` — data is a flat object |
|
|
83
|
+
| `sentori.captureMessage(msg)` | `sentori.warn(name, data)` — a named sub-health report |
|
|
84
|
+
| `sentori.track(name, props)` | `sentori.trace(name, props)` |
|
|
85
|
+
| `sentori.addBreadcrumb({ type, data })` | `sentori.trace(name, data, { quiet: true })` — ring-only |
|
|
86
|
+
| `sentori.setUser({ id, linkBy: { email }, name })` | `sentori.user({ id, email, name })` — flat shape |
|
|
87
|
+
| `sentori.setUser(null)` | `sentori.user(null)` |
|
|
88
|
+
| `sentori.setFeatureFlag(key, value)` | `sentori.context({ [key]: value })` |
|
|
89
|
+
| — | `sentori.assert(name, ok, data?)` — **new**: production assertion; failure reports, never halts |
|
|
90
|
+
| — | `sentori.probe(ref, data?)` — **new**: regression tripwire; plant in the branch that used to break |
|
|
91
|
+
|
|
92
|
+
Every verb returns immediately, never returns a Promise, and never
|
|
93
|
+
throws — the queue is the contract.
|
|
94
|
+
|
|
95
|
+
## 5. Components and helpers
|
|
96
|
+
|
|
97
|
+
| 4.x | 5.x |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `<sentori.RageTapCapture>` | `import { RageTapCapture }` — named export, same wrapper role |
|
|
100
|
+
| `<FeedbackButton />` | removed |
|
|
101
|
+
| `sentori.registerMaskQuery(fn)` | removed — screenshot masking returns with the privacy pass |
|
|
102
|
+
| `useTraceNavigation(ref)` | unchanged (feeds `nav` signals + screen names on warns) |
|
|
103
|
+
| `triggerNativeCrash()` | unchanged (dev-panel helper) |
|
|
104
|
+
| `ErrorBoundary` | unchanged |
|
|
105
|
+
| `push.*` namespace | unchanged |
|
|
106
|
+
|
|
107
|
+
## 6. Build pipeline (CLI 1.x)
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# Sourcemaps — upload failures print a friendly notice and exit 0
|
|
111
|
+
# so they never block a release; pass --strict to opt into a
|
|
112
|
+
# non-zero exit. Events received before the upload are
|
|
113
|
+
# re-symbolicated automatically afterwards.
|
|
114
|
+
sentori-cli upload sourcemap dist/main.jsbundle.map \
|
|
115
|
+
--release "myapp@1.4.2+381" --token $SENTORI_API_TOKEN
|
|
116
|
+
|
|
117
|
+
# Probe tripwires — static-scans your source for sentori.probe()
|
|
118
|
+
# call sites and registers them against the release, so a silent
|
|
119
|
+
# probe is provable "fix holding".
|
|
120
|
+
sentori-cli probes sync --release "myapp@1.4.2+381" --dir src
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## 7. Worked example
|
|
124
|
+
|
|
125
|
+
The first migration (Qualcomm insight-mobile) touched ten files
|
|
126
|
+
and took the shape above verbatim; the diff is a useful reference:
|
|
127
|
+
`captureException`→`error` in error reporters, `track`/
|
|
128
|
+
`addBreadcrumb`→`trace` in analytics shims, `setUser` flattening
|
|
129
|
+
in the auth listener, `setFeatureFlag`→one `context()` call at
|
|
130
|
+
boot, plus one new `probe()` planted on a previously-fixed bug's
|
|
131
|
+
failure branch.
|
package/README.md
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
# @goliapkg/sentori-react-native
|
|
2
2
|
|
|
3
|
-
React Native SDK for [Sentori](https://sentori.golia.jp) —
|
|
4
|
-
|
|
3
|
+
React Native SDK for [Sentori](https://sentori.golia.jp) — the
|
|
4
|
+
self-hosted crash + warning monitor for mobile apps. JS layer +
|
|
5
|
+
iOS Swift + Android Kotlin native, distributed as an Expo module
|
|
5
6
|
(works on bare RN too).
|
|
6
7
|
|
|
8
|
+
Eight verbs are the whole API. Every one is synchronous, returns
|
|
9
|
+
immediately, and can never throw into your app.
|
|
10
|
+
|
|
11
|
+
Upgrading from 4.x? See [MIGRATION.md](./MIGRATION.md).
|
|
12
|
+
|
|
7
13
|
## Install
|
|
8
14
|
|
|
9
15
|
```sh
|
|
@@ -17,47 +23,51 @@ cd ios && pod install --repo-update
|
|
|
17
23
|
import { sentori } from '@goliapkg/sentori-react-native'
|
|
18
24
|
|
|
19
25
|
sentori.init({
|
|
20
|
-
token: '
|
|
26
|
+
token: 'st_…', // ingest token, Settings → Tokens
|
|
27
|
+
ingestUrl: 'https://sentori.example.com',
|
|
21
28
|
release: 'my-app@1.2.3',
|
|
22
29
|
environment: 'prod',
|
|
23
|
-
capture: {
|
|
24
|
-
replay: { mode: 'wireframe', hz: 1 },
|
|
25
|
-
},
|
|
26
30
|
})
|
|
27
31
|
|
|
28
|
-
sentori.
|
|
32
|
+
sentori.user({ id, email, name }) // drives breadth × depth stats
|
|
33
|
+
sentori.context({ tenant: 'acme' }) // ambient tags on every event
|
|
34
|
+
|
|
35
|
+
sentori.error(new Error('boom')) // what broke
|
|
36
|
+
sentori.warn('pay.gateway-retry', data) // where users hurt
|
|
37
|
+
sentori.trace('checkout.start', data) // what happened
|
|
38
|
+
sentori.assert('total-positive', ok) // what should hold (never halts)
|
|
39
|
+
sentori.probe('BUG-123', data) // did that bug come back
|
|
29
40
|
```
|
|
30
41
|
|
|
31
|
-
Auto-wired:
|
|
42
|
+
Auto-wired (no configuration):
|
|
32
43
|
|
|
33
44
|
- JS `error` / `unhandledrejection` global hooks
|
|
34
|
-
- iOS `NSException`
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
-
|
|
38
|
-
|
|
45
|
+
- iOS `NSException` + Android uncaught-exception handlers
|
|
46
|
+
- Warn scenario detectors: rage taps, long freezes, slow cold start
|
|
47
|
+
(slow API stays opt-in) — tune with `init({ detect })`
|
|
48
|
+
- Signal ring: recent taps, navigation, traces ride along on every
|
|
49
|
+
error/warn as the "what the user was doing" timeline
|
|
50
|
+
- B-type replay: rolling wireframe buffer, shipped only when an
|
|
51
|
+
error/warn actually fires (`init({ replaySeconds })`)
|
|
39
52
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
```ts
|
|
43
|
-
sentori.setUser({ linkBy: { email: user.email } })
|
|
44
|
-
```
|
|
53
|
+
Identity is hashed on-device with a salted hash — the server never
|
|
54
|
+
sees the raw email.
|
|
45
55
|
|
|
46
|
-
|
|
47
|
-
sees the raw email / phone.
|
|
56
|
+
## React extras
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
Sentry.captureException(err)
|
|
58
|
+
```tsx
|
|
59
|
+
import {
|
|
60
|
+
ErrorBoundary, // React idiom for the error verb
|
|
61
|
+
RageTapCapture, // wrap your root to feed the rage-tap detector
|
|
62
|
+
useTraceNavigation, // pass your react-navigation ref
|
|
63
|
+
} from '@goliapkg/sentori-react-native'
|
|
56
64
|
```
|
|
57
65
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
66
|
+
## Build pipeline
|
|
67
|
+
|
|
68
|
+
`@goliapkg/sentori-cli` uploads sourcemaps (failures never block a
|
|
69
|
+
release — friendly notice, exit 0) and registers `probe()`
|
|
70
|
+
tripwires per release. See MIGRATION.md §6.
|
|
61
71
|
|
|
62
72
|
## License
|
|
63
73
|
|
|
@@ -79,6 +79,14 @@ class SentoriModule : Module() {
|
|
|
79
79
|
SentoriScreenshotCapture.captureScreenshotWithMask(maskedIds)
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
// v5.1 — low-bitrate replay frame (screens ring). Long edge +
|
|
83
|
+
// quality come from JS so the frame budget is tunable without
|
|
84
|
+
// a native release.
|
|
85
|
+
AsyncFunction("captureReplayFrame") {
|
|
86
|
+
maskedIds: List<String>, longEdgePx: Double, quality: Double ->
|
|
87
|
+
SentoriScreenshotCapture.captureReplayFrame(maskedIds, longEdgePx, quality)
|
|
88
|
+
}
|
|
89
|
+
|
|
82
90
|
// Watchdog is opt-in from JS so the host app picks the
|
|
83
91
|
// trade-off — stricter detection vs noise from the Metro
|
|
84
92
|
// debugger pausing the main thread. Pass `force: true` to
|
|
@@ -147,9 +147,33 @@ object SentoriScreenshotCapture {
|
|
|
147
147
|
return mapOf("base64" to base64, "mediaType" to mediaType)
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
/// v5.1 — low-bitrate replay frame: the screenshot pipeline with
|
|
151
|
+
/// the long edge and quality dialed down by the caller. A replay
|
|
152
|
+
/// ring ticks this a few times per minute, so a frame is budgeted
|
|
153
|
+
/// in tens of KB, not the crash screenshot's hundred.
|
|
154
|
+
@JvmStatic
|
|
155
|
+
fun captureReplayFrame(
|
|
156
|
+
maskedIds: List<String>,
|
|
157
|
+
longEdgePx: Double,
|
|
158
|
+
quality: Double,
|
|
159
|
+
): Map<String, String>? {
|
|
160
|
+
val activity = SentoriForegroundActivity.current() ?: return null
|
|
161
|
+
val window = activity.window ?: return null
|
|
162
|
+
val pct = (quality * 100).toInt().coerceIn(1, 100)
|
|
163
|
+
val (base64, mediaType) =
|
|
164
|
+
captureScreen(window, maskedIds.toHashSet(), longEdgePx.toFloat(), pct)
|
|
165
|
+
?: return null
|
|
166
|
+
return mapOf("base64" to base64, "mediaType" to mediaType)
|
|
167
|
+
}
|
|
168
|
+
|
|
150
169
|
// ── screenshot ────────────────────────────────────────────────
|
|
151
170
|
|
|
152
|
-
private fun captureScreen(
|
|
171
|
+
private fun captureScreen(
|
|
172
|
+
window: Window,
|
|
173
|
+
maskedIds: Set<String>,
|
|
174
|
+
longEdgePx: Float = MAX_LONG_EDGE_PX.toFloat(),
|
|
175
|
+
qualityPct: Int = -1,
|
|
176
|
+
): Pair<String, String>? {
|
|
153
177
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
|
154
178
|
// PixelCopy is API 24+. Older Android: fall back to a
|
|
155
179
|
// `View.draw(Canvas)` path that *must* run on main and
|
|
@@ -175,7 +199,7 @@ object SentoriScreenshotCapture {
|
|
|
175
199
|
|
|
176
200
|
// Long-edge scale.
|
|
177
201
|
val longEdge = maxOf(w, h).toFloat()
|
|
178
|
-
val scale = if (longEdge >
|
|
202
|
+
val scale = if (longEdge > longEdgePx) longEdgePx / longEdge else 1f
|
|
179
203
|
val outW = (w * scale).toInt().coerceAtLeast(1)
|
|
180
204
|
val outH = (h * scale).toInt().coerceAtLeast(1)
|
|
181
205
|
val bitmap = Bitmap.createBitmap(outW, outH, Bitmap.Config.ARGB_8888)
|
|
@@ -244,10 +268,18 @@ object SentoriScreenshotCapture {
|
|
|
244
268
|
val mediaType: String
|
|
245
269
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
246
270
|
// Android 11+: native WEBP_LOSSY ~30% smaller than JPEG q=70.
|
|
247
|
-
bitmap.compress(
|
|
271
|
+
bitmap.compress(
|
|
272
|
+
Bitmap.CompressFormat.WEBP_LOSSY,
|
|
273
|
+
if (qualityPct in 1..100) qualityPct else WEBP_QUALITY,
|
|
274
|
+
baos,
|
|
275
|
+
)
|
|
248
276
|
mediaType = "image/webp"
|
|
249
277
|
} else {
|
|
250
|
-
bitmap.compress(
|
|
278
|
+
bitmap.compress(
|
|
279
|
+
Bitmap.CompressFormat.JPEG,
|
|
280
|
+
if (qualityPct in 1..100) qualityPct else JPEG_QUALITY,
|
|
281
|
+
baos,
|
|
282
|
+
)
|
|
251
283
|
mediaType = "image/jpeg"
|
|
252
284
|
}
|
|
253
285
|
bitmap.recycle()
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Foundation
|
|
2
|
+
import MachO
|
|
2
3
|
|
|
3
4
|
/// Static crash handler — captures NSException and writes one JSON file
|
|
4
5
|
/// per crash to <Documents>/sentori/pending/<uuid>.json. JS drains that
|
|
@@ -87,7 +88,7 @@ import Foundation
|
|
|
87
88
|
"error": [
|
|
88
89
|
"type": exception.name.rawValue,
|
|
89
90
|
"message": exception.reason ?? "",
|
|
90
|
-
"stack": frames(from: exception.callStackSymbols),
|
|
91
|
+
"stack": frames(from: exception.callStackSymbols, addresses: exception.callStackReturnAddresses),
|
|
91
92
|
"cause": NSNull(),
|
|
92
93
|
],
|
|
93
94
|
"fingerprint": [String](),
|
|
@@ -171,8 +172,11 @@ import Foundation
|
|
|
171
172
|
/// "1 AppName 0x0001a0b0 -[ClassName method:] + 100"
|
|
172
173
|
/// We don't have file/line info in raw symbol output; sourcemap-style
|
|
173
174
|
/// symbolication for native happens server-side (Phase 8+).
|
|
174
|
-
private static func frames(
|
|
175
|
-
|
|
175
|
+
private static func frames(
|
|
176
|
+
from symbols: [String],
|
|
177
|
+
addresses: [NSNumber] = []
|
|
178
|
+
) -> [[String: Any]] {
|
|
179
|
+
return symbols.enumerated().map { (i, sym) -> [[String: Any]].Element in
|
|
176
180
|
let parts = sym.split(separator: " ", omittingEmptySubsequences: true).map(String.init)
|
|
177
181
|
let module = parts.count > 1 ? parts[1] : "<unknown>"
|
|
178
182
|
let function = parts.count > 3 ? parts.dropFirst(3).joined(separator: " ") : "<anonymous>"
|
|
@@ -181,12 +185,52 @@ import Foundation
|
|
|
181
185
|
&& !module.contains("CoreFoundation")
|
|
182
186
|
&& !module.contains("libsystem")
|
|
183
187
|
&& !module.contains("libobjc")
|
|
184
|
-
|
|
188
|
+
var frame: [String: Any] = [
|
|
185
189
|
"function": function,
|
|
186
190
|
"file": module,
|
|
187
191
|
"line": 0,
|
|
188
192
|
"inApp": inApp,
|
|
189
193
|
]
|
|
194
|
+
// v5.1 — raw address + owning image (base, LC_UUID) so the
|
|
195
|
+
// server can resolve the frame through the release's dSYM
|
|
196
|
+
// slice. dladdr + load-command walk are async-signal-
|
|
197
|
+
// unsafe in general but fine here: NSException handlers
|
|
198
|
+
// run on a live runtime, same as the UIKit capture below.
|
|
199
|
+
if i < addresses.count {
|
|
200
|
+
let addr = addresses[i].uintValue
|
|
201
|
+
var info = Dl_info()
|
|
202
|
+
if dladdr(UnsafeRawPointer(bitPattern: addr), &info) != 0,
|
|
203
|
+
let fbase = info.dli_fbase {
|
|
204
|
+
frame["addr"] = UInt64(addr)
|
|
205
|
+
frame["imageBase"] = UInt64(UInt(bitPattern: fbase))
|
|
206
|
+
if let uuid = imageUuid(atBase: fbase) {
|
|
207
|
+
frame["imageUuid"] = uuid
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return frame
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/// LC_UUID of the Mach-O image loaded at `base` — the identity a
|
|
216
|
+
/// dSYM slice is matched by. Walks the load commands off the
|
|
217
|
+
/// in-memory header; read-only, bounded, no allocation beyond
|
|
218
|
+
/// the hex string.
|
|
219
|
+
private static func imageUuid(atBase base: UnsafeRawPointer) -> String? {
|
|
220
|
+
let header = base.assumingMemoryBound(to: mach_header_64.self)
|
|
221
|
+
guard header.pointee.magic == MH_MAGIC_64 else { return nil }
|
|
222
|
+
var cursor = base.advanced(by: MemoryLayout<mach_header_64>.size)
|
|
223
|
+
for _ in 0..<header.pointee.ncmds {
|
|
224
|
+
let cmd = cursor.assumingMemoryBound(to: load_command.self)
|
|
225
|
+
if cmd.pointee.cmd == LC_UUID {
|
|
226
|
+
let uuidCmd = cursor.assumingMemoryBound(to: uuid_command.self)
|
|
227
|
+
let u = uuidCmd.pointee.uuid
|
|
228
|
+
let bytes = [u.0, u.1, u.2, u.3, u.4, u.5, u.6, u.7,
|
|
229
|
+
u.8, u.9, u.10, u.11, u.12, u.13, u.14, u.15]
|
|
230
|
+
return bytes.map { String(format: "%02X", $0) }.joined()
|
|
231
|
+
}
|
|
232
|
+
cursor = cursor.advanced(by: Int(cmd.pointee.cmdsize))
|
|
190
233
|
}
|
|
234
|
+
return nil
|
|
191
235
|
}
|
|
192
236
|
}
|
package/ios/SentoriModule.swift
CHANGED
|
@@ -77,6 +77,18 @@ public class SentoriModule: Module {
|
|
|
77
77
|
return SentoriScreenshotCapture.captureScreenshotWithMask(maskedIds: maskedIds)
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
// v5.1 — low-bitrate replay frame (screens ring). Long edge +
|
|
81
|
+
// quality come from JS so the frame budget is tunable without
|
|
82
|
+
// a native release.
|
|
83
|
+
AsyncFunction("captureReplayFrame") {
|
|
84
|
+
(maskedIds: [String], longEdgePx: Double, quality: Double) -> [String: String]? in
|
|
85
|
+
return SentoriScreenshotCapture.captureReplayFrame(
|
|
86
|
+
maskedIds: maskedIds,
|
|
87
|
+
longEdgePx: longEdgePx,
|
|
88
|
+
quality: quality
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
80
92
|
// Phase 22 sub-E: opt-in iOS hang watchdog. Same JS function
|
|
81
93
|
// name as Android (sub-D) so the host app calls
|
|
82
94
|
// `startAnrWatchdog(...)` once, both platforms react.
|
|
@@ -81,6 +81,35 @@ import UIKit
|
|
|
81
81
|
return result
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/// v5.1 — replay-frame capture: the screenshot pipeline with the
|
|
85
|
+
/// long edge and JPEG quality dialed down by the caller. A replay
|
|
86
|
+
/// ring ticks this a few times per minute, so a frame is budgeted
|
|
87
|
+
/// in tens of KB, not the crash screenshot's hundred.
|
|
88
|
+
@objc public static func captureReplayFrame(
|
|
89
|
+
maskedIds: [String],
|
|
90
|
+
longEdgePx: Double,
|
|
91
|
+
quality: Double
|
|
92
|
+
) -> [String: String]? {
|
|
93
|
+
let run: () -> [String: String]? = {
|
|
94
|
+
guard let window = keyWindowDiag().window else { return nil }
|
|
95
|
+
guard
|
|
96
|
+
let jpeg = renderJpegBase64(
|
|
97
|
+
window: window,
|
|
98
|
+
maskedIds: Set(maskedIds),
|
|
99
|
+
longEdgePx: CGFloat(longEdgePx),
|
|
100
|
+
quality: CGFloat(quality)
|
|
101
|
+
)
|
|
102
|
+
else { return nil }
|
|
103
|
+
return ["base64": jpeg, "mediaType": "image/jpeg"]
|
|
104
|
+
}
|
|
105
|
+
if Thread.isMainThread {
|
|
106
|
+
return run()
|
|
107
|
+
}
|
|
108
|
+
var result: [String: String]?
|
|
109
|
+
DispatchQueue.main.sync { result = run() }
|
|
110
|
+
return result
|
|
111
|
+
}
|
|
112
|
+
|
|
84
113
|
private static func captureWithMaskSync(maskedIds: [String]) -> [String: String]? {
|
|
85
114
|
guard let window = keyWindowDiag().window else {
|
|
86
115
|
lastDiagPath = "window.null"
|
|
@@ -191,11 +220,13 @@ import UIKit
|
|
|
191
220
|
|
|
192
221
|
private static func renderJpegBase64(
|
|
193
222
|
window: UIWindow,
|
|
194
|
-
maskedIds: Set<String> = []
|
|
223
|
+
maskedIds: Set<String> = [],
|
|
224
|
+
longEdgePx: CGFloat = maxLongEdgePx,
|
|
225
|
+
quality: CGFloat = jpegQuality
|
|
195
226
|
) -> String? {
|
|
196
227
|
let bounds = window.bounds
|
|
197
228
|
let longEdge = max(bounds.width, bounds.height)
|
|
198
|
-
let scale: CGFloat = longEdge >
|
|
229
|
+
let scale: CGFloat = longEdge > longEdgePx ? longEdgePx / longEdge : 1.0
|
|
199
230
|
let outSize = CGSize(width: bounds.width * scale, height: bounds.height * scale)
|
|
200
231
|
guard outSize.width > 1, outSize.height > 1 else { return nil }
|
|
201
232
|
|
|
@@ -230,7 +261,7 @@ import UIKit
|
|
|
230
261
|
}
|
|
231
262
|
}
|
|
232
263
|
}
|
|
233
|
-
guard let data = image.jpegData(compressionQuality:
|
|
264
|
+
guard let data = image.jpegData(compressionQuality: quality) else {
|
|
234
265
|
return nil
|
|
235
266
|
}
|
|
236
267
|
return data.base64EncodedString()
|
package/lib/config.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export type Config = {
|
|
|
14
14
|
};
|
|
15
15
|
/** B-type replay rolling buffer, seconds. 0 disables. */
|
|
16
16
|
replaySeconds: number;
|
|
17
|
+
replayScreens: boolean;
|
|
17
18
|
/** Sentori console output gate. Default `warn`: silent on the
|
|
18
19
|
* host's console unless something is genuinely broken. */
|
|
19
20
|
logLevel?: LogLevel;
|
package/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAElE,MAAM,MAAM,MAAM,GAAG;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,qEAAqE;IACrE,MAAM,EAAE;QACN,OAAO,EAAE,OAAO,CAAC;QACjB,UAAU,EAAE,OAAO,CAAC;QACpB,aAAa,EAAE,OAAO,CAAC;QACvB,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC;IACF,yDAAyD;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB;+DAC2D;IAC3D,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;8DAC0D;IAC1D,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,SAAS,GAAG,IAAI,CAAC;CACrD,CAAC;AAIF,eAAO,MAAM,SAAS,WAAY,MAAM,KAAG,IAE1C,CAAC;AAEF,eAAO,MAAM,SAAS,QAAO,MAAM,GAAG,IAAe,CAAC;AAEtD,eAAO,MAAM,aAAa,QAAO,OAA2B,CAAC;AAE7D,eAAO,MAAM,eAAe,QAAO,IAElC,CAAC"}
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAElE,MAAM,MAAM,MAAM,GAAG;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,qEAAqE;IACrE,MAAM,EAAE;QACN,OAAO,EAAE,OAAO,CAAC;QACjB,UAAU,EAAE,OAAO,CAAC;QACpB,aAAa,EAAE,OAAO,CAAC;QACvB,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC;IACF,yDAAyD;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB;+DAC2D;IAC3D,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;8DAC0D;IAC1D,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,SAAS,GAAG,IAAI,CAAC;CACrD,CAAC;AAIF,eAAO,MAAM,SAAS,WAAY,MAAM,KAAG,IAE1C,CAAC;AAEF,eAAO,MAAM,SAAS,QAAO,MAAM,GAAG,IAAe,CAAC;AAEtD,eAAO,MAAM,aAAa,QAAO,OAA2B,CAAC;AAE7D,eAAO,MAAM,eAAe,QAAO,IAElC,CAAC"}
|
package/lib/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,kEAAkE;AAClE,4DAA4D;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,kEAAkE;AAClE,4DAA4D;AA4B5D,IAAI,OAAO,GAAkB,IAAI,CAAC;AAElC,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAc,EAAQ,EAAE;IAChD,OAAO,GAAG,MAAM,CAAC;AACnB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAG,GAAkB,EAAE,CAAC,OAAO,CAAC;AAEtD,MAAM,CAAC,MAAM,aAAa,GAAG,GAAY,EAAE,CAAC,OAAO,KAAK,IAAI,CAAC;AAE7D,MAAM,CAAC,MAAM,eAAe,GAAG,GAAS,EAAE;IACxC,OAAO,GAAG,IAAI,CAAC;AACjB,CAAC,CAAC"}
|
package/lib/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export default sentori;
|
|
|
30
30
|
export { init } from './init';
|
|
31
31
|
export { ErrorBoundary } from './error-boundary';
|
|
32
32
|
export { RageTapCapture } from './rage-tap';
|
|
33
|
+
export { registerMaskQuery } from './mask';
|
|
33
34
|
export { triggerNativeCrash } from './native';
|
|
34
35
|
export { type NavigationRefLike, useTraceNavigation } from './navigation';
|
|
35
36
|
export type { AssertStat, Device, EventData, EventKind, Frame, InitConfig, SentoriApi, SentoriError, Signal, Surface, TraceOptions, User, WireEvent, WirePayload, } from '@goliapkg/sentori-core';
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EACV,SAAS,EACT,UAAU,EACV,YAAY,EACZ,IAAI,EACL,MAAM,wBAAwB,CAAC;AAEhC,eAAO,MAAM,OAAO;IAClB,2DAA2D;IAC3D,IAAI,WAAW,UAAU,KAAG,IAAI;IAGhC;;4CAEwC;IACxC,IAAI,MAAM,IAAI,GAAG,IAAI,KAAG,IAAI;IAG5B;8CAC0C;IAC1C,OAAO,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,IAAI;IAI/C;sEACkE;IAClE,KAAK,QAAQ,OAAO,SAAS,SAAS,KAAG,MAAM;IAC/C,kDAAkD;IAClD,IAAI,SAAS,MAAM,SAAS,SAAS,KAAG,MAAM;IAC9C;6DACyD;IACzD,KAAK,SAAS,MAAM,SAAS,SAAS,SAAS,YAAY,KAAG,MAAM;IAEpE;;mDAE+C;IAC/C,MAAM,SAAS,MAAM,MAAM,OAAO,SAAS,SAAS,KAAG,MAAM;IAE7D;;qCAEiC;IACjC,KAAK,QAAQ,MAAM,SAAS,SAAS,KAAG,MAAM;CAC/C,CAAC;eAEa,OAAO;AAEtB,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAG5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,KAAK,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAG1E,YAAY,EACV,UAAU,EACV,MAAM,EACN,SAAS,EACT,SAAS,EACT,KAAK,EACL,UAAU,EACV,UAAU,EACV,YAAY,EACZ,MAAM,EACN,OAAO,EACP,YAAY,EACZ,IAAI,EACJ,SAAS,EACT,WAAW,GACZ,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,WAAW,EACX,KAAK,QAAQ,EACb,WAAW,EACX,KAAK,YAAY,EACjB,eAAe,GAChB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,KAAK,IAAI,MAAM,QAAQ,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EACV,SAAS,EACT,UAAU,EACV,YAAY,EACZ,IAAI,EACL,MAAM,wBAAwB,CAAC;AAEhC,eAAO,MAAM,OAAO;IAClB,2DAA2D;IAC3D,IAAI,WAAW,UAAU,KAAG,IAAI;IAGhC;;4CAEwC;IACxC,IAAI,MAAM,IAAI,GAAG,IAAI,KAAG,IAAI;IAG5B;8CAC0C;IAC1C,OAAO,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,IAAI;IAI/C;sEACkE;IAClE,KAAK,QAAQ,OAAO,SAAS,SAAS,KAAG,MAAM;IAC/C,kDAAkD;IAClD,IAAI,SAAS,MAAM,SAAS,SAAS,KAAG,MAAM;IAC9C;6DACyD;IACzD,KAAK,SAAS,MAAM,SAAS,SAAS,SAAS,YAAY,KAAG,MAAM;IAEpE;;mDAE+C;IAC/C,MAAM,SAAS,MAAM,MAAM,OAAO,SAAS,SAAS,KAAG,MAAM;IAE7D;;qCAEiC;IACjC,KAAK,QAAQ,MAAM,SAAS,SAAS,KAAG,MAAM;CAC/C,CAAC;eAEa,OAAO;AAEtB,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAG5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAG3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,KAAK,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAG1E,YAAY,EACV,UAAU,EACV,MAAM,EACN,SAAS,EACT,SAAS,EACT,KAAK,EACL,UAAU,EACV,UAAU,EACV,YAAY,EACZ,MAAM,EACN,OAAO,EACP,YAAY,EACZ,IAAI,EACJ,SAAS,EACT,WAAW,GACZ,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,WAAW,EACX,KAAK,QAAQ,EACb,WAAW,EACX,KAAK,YAAY,EACjB,eAAe,GAChB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,KAAK,IAAI,MAAM,QAAQ,CAAC"}
|
package/lib/index.js
CHANGED
|
@@ -48,6 +48,9 @@ export default sentori;
|
|
|
48
48
|
export { init } from './init';
|
|
49
49
|
export { ErrorBoundary } from './error-boundary';
|
|
50
50
|
export { RageTapCapture } from './rage-tap';
|
|
51
|
+
// Privacy: register a query returning nativeIDs that must be
|
|
52
|
+
// blacked out in every captured frame (visual replay, screenshots).
|
|
53
|
+
export { registerMaskQuery } from './mask';
|
|
51
54
|
// Dev-panel helper: crash the native layer on purpose to exercise
|
|
52
55
|
// the pending-crash upload pipeline. Not part of the 8-verb surface.
|
|
53
56
|
export { triggerNativeCrash } from './native';
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,EAAE;AACF,uEAAuE;AACvE,oEAAoE;AACpE,8CAA8C;AAC9C,EAAE;AACF,gEAAgE;AAChE,oEAAoE;AACpE,qEAAqE;AACrE,8DAA8D;AAE9D,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAShC,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,2DAA2D;IAC3D,IAAI,EAAE,CAAC,MAAkB,EAAQ,EAAE;QACjC,IAAI,CAAC,MAAM,CAAC,CAAC;IACf,CAAC;IACD;;4CAEwC;IACxC,IAAI,EAAE,CAAC,CAAc,EAAQ,EAAE;QAC7B,OAAO,CAAC,CAAC,CAAC,CAAC;IACb,CAAC;IACD;8CAC0C;IAC1C,OAAO,EAAE,CAAC,KAA8B,EAAQ,EAAE;QAChD,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IAED;sEACkE;IAClE,KAAK,EAAE,CAAC,GAAY,EAAE,IAAgB,EAAU,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC;IACzE,kDAAkD;IAClD,IAAI,EAAE,CAAC,IAAY,EAAE,IAAgB,EAAU,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC;IACxE;6DACyD;IACzD,KAAK,EAAE,CAAC,IAAY,EAAE,IAAgB,EAAE,IAAmB,EAAU,EAAE,CACrE,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC;IAC/B;;mDAE+C;IAC/C,MAAM,EAAE,CAAC,IAAY,EAAE,EAAW,EAAE,IAAgB,EAAU,EAAE,CAC9D,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC;IAC9B;;qCAEiC;IACjC,KAAK,EAAE,CAAC,GAAW,EAAE,IAAgB,EAAU,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC;CACzE,CAAC;AAEF,eAAe,OAAO,CAAC;AAEvB,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,kEAAkE;AAClE,qEAAqE;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAA0B,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAoB1E,mDAAmD;AACnD,OAAO,EACL,WAAW,EAEX,WAAW,EAEX,eAAe,GAChB,MAAM,wBAAwB,CAAC;AAEhC,qEAAqE;AACrE,OAAO,KAAK,IAAI,MAAM,QAAQ,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,EAAE;AACF,uEAAuE;AACvE,oEAAoE;AACpE,8CAA8C;AAC9C,EAAE;AACF,gEAAgE;AAChE,oEAAoE;AACpE,qEAAqE;AACrE,8DAA8D;AAE9D,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAShC,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,2DAA2D;IAC3D,IAAI,EAAE,CAAC,MAAkB,EAAQ,EAAE;QACjC,IAAI,CAAC,MAAM,CAAC,CAAC;IACf,CAAC;IACD;;4CAEwC;IACxC,IAAI,EAAE,CAAC,CAAc,EAAQ,EAAE;QAC7B,OAAO,CAAC,CAAC,CAAC,CAAC;IACb,CAAC;IACD;8CAC0C;IAC1C,OAAO,EAAE,CAAC,KAA8B,EAAQ,EAAE;QAChD,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IAED;sEACkE;IAClE,KAAK,EAAE,CAAC,GAAY,EAAE,IAAgB,EAAU,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC;IACzE,kDAAkD;IAClD,IAAI,EAAE,CAAC,IAAY,EAAE,IAAgB,EAAU,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC;IACxE;6DACyD;IACzD,KAAK,EAAE,CAAC,IAAY,EAAE,IAAgB,EAAE,IAAmB,EAAU,EAAE,CACrE,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC;IAC/B;;mDAE+C;IAC/C,MAAM,EAAE,CAAC,IAAY,EAAE,EAAW,EAAE,IAAgB,EAAU,EAAE,CAC9D,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC;IAC9B;;qCAEiC;IACjC,KAAK,EAAE,CAAC,GAAW,EAAE,IAAgB,EAAU,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC;CACzE,CAAC;AAEF,eAAe,OAAO,CAAC;AAEvB,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,6DAA6D;AAC7D,oEAAoE;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3C,kEAAkE;AAClE,qEAAqE;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAA0B,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAoB1E,mDAAmD;AACnD,OAAO,EACL,WAAW,EAEX,WAAW,EAEX,eAAe,GAChB,MAAM,wBAAwB,CAAC;AAEhC,qEAAqE;AACrE,OAAO,KAAK,IAAI,MAAM,QAAQ,CAAC"}
|
package/lib/init.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAkBzD,eAAO,MAAM,IAAI,0CAgGf,CAAC;AAeH,eAAO,MAAM,eAAe,QAAO,IAElC,CAAC"}
|
package/lib/init.js
CHANGED
|
@@ -14,6 +14,7 @@ import { checkColdStart } from './mobile-vitals';
|
|
|
14
14
|
import { markNativeJsBridgeReady, setNativeConfig } from './native';
|
|
15
15
|
import { shipNativePending } from './native-pending';
|
|
16
16
|
import { drainReplay, startReplay } from './replay';
|
|
17
|
+
import { drainScreenReplay, startScreenReplay } from './replay-screens';
|
|
17
18
|
import { drainOfflineQueue, startTransport, uploadAttachment } from './transport';
|
|
18
19
|
let _initialized = false;
|
|
19
20
|
export const init = safeFn('init', (config) => {
|
|
@@ -43,6 +44,7 @@ export const init = safeFn('init', (config) => {
|
|
|
43
44
|
slowApi: config.detect?.slowApi ?? false,
|
|
44
45
|
},
|
|
45
46
|
replaySeconds: config.replaySeconds ?? 30,
|
|
47
|
+
replayScreens: config.replayScreens ?? false,
|
|
46
48
|
beforeSend: config.beforeSend,
|
|
47
49
|
});
|
|
48
50
|
setLogLevel(config.logLevel ?? 'warn');
|
|
@@ -60,18 +62,28 @@ export const init = safeFn('init', (config) => {
|
|
|
60
62
|
const replaySeconds = config.replaySeconds ?? 30;
|
|
61
63
|
if (replaySeconds > 0) {
|
|
62
64
|
startReplay({ mode: 'wireframe' });
|
|
65
|
+
// Visual ring is opt-in: screenshots can carry user content.
|
|
66
|
+
if (config.replayScreens === true)
|
|
67
|
+
startScreenReplay(replaySeconds);
|
|
63
68
|
registerEmitHook((event) => {
|
|
64
69
|
if (event.kind !== 'error' && event.kind !== 'warn')
|
|
65
70
|
return;
|
|
66
71
|
if (!event.id)
|
|
67
72
|
return;
|
|
68
73
|
const lines = drainReplay();
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
if (lines) {
|
|
75
|
+
const base64 = base64Encode(lines);
|
|
76
|
+
if (base64) {
|
|
77
|
+
void uploadAttachment(event.id, 'replay', { base64, mediaType: 'application/x-sentori-replay' }, { source: 'js' });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const frames = drainScreenReplay();
|
|
81
|
+
if (frames) {
|
|
82
|
+
const base64 = base64Encode(frames);
|
|
83
|
+
if (base64) {
|
|
84
|
+
void uploadAttachment(event.id, 'screens', { base64, mediaType: 'application/x-sentori-screens' }, { source: 'js' });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
75
87
|
});
|
|
76
88
|
}
|
|
77
89
|
// Native side: hand over release/environment for the crash-file
|
package/lib/init.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../src/init.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,mEAAmE;AACnE,6DAA6D;AAC7D,iCAAiC;AAEjC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAG7D,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAElF,IAAI,YAAY,GAAG,KAAK,CAAC;AAEzB,MAAM,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,MAAkB,EAAQ,EAAE;IAC9D,IAAI,YAAY;QAAE,OAAO;IAEzB,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7E,sCAAsC;QACtC,OAAO,CAAC,IAAI,CAAC,wDAAwD,CAAC,CAAC;QACvE,OAAO;IACT,CAAC;IACD,IAAI,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1E,sCAAsC;QACtC,OAAO,CAAC,IAAI,CAAC,4DAA4D,CAAC,CAAC;QAC3E,OAAO;IACT,CAAC;IAED,YAAY,GAAG,IAAI,CAAC;IAEpB,SAAS,CAAC;QACR,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QAC/C,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,EAAE;QAC7B,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,YAAY;QAC/C,OAAO,EAAE,IAAI;QACb,MAAM,EAAE;YACN,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,IAAI,IAAI;YACvC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,IAAI,IAAI;YAC7C,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,IAAI,IAAI;YACnD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,IAAI,KAAK;SACzC;QACD,aAAa,EAAE,MAAM,CAAC,aAAa,IAAI,EAAE;QACzC,aAAa,EAAE,MAAM,CAAC,aAAa,IAAI,KAAK;QAC5C,UAAU,EAAE,MAAM,CAAC,UAAU;KAC9B,CAAC,CAAC;IACH,WAAW,CAAC,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,CAAC;IAEvC,mDAAmD;IACnD,oBAAoB,EAAE,CAAC;IACvB,qBAAqB,EAAE,CAAC;IACxB,qBAAqB,EAAE,CAAC;IACxB,uBAAuB,EAAE,CAAC;IAE1B,+DAA+D;IAC/D,2DAA2D;IAC3D,IAAI,MAAM,CAAC,MAAM,EAAE,UAAU,KAAK,KAAK;QAAE,oBAAoB,EAAE,CAAC;IAEhE,mEAAmE;IACnE,8DAA8D;IAC9D,MAAM,aAAa,GAAG,MAAM,CAAC,aAAa,IAAI,EAAE,CAAC;IACjD,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;QACtB,WAAW,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;QACnC,6DAA6D;QAC7D,IAAI,MAAM,CAAC,aAAa,KAAK,IAAI;YAAE,iBAAiB,CAAC,aAAa,CAAC,CAAC;QACpE,gBAAgB,CAAC,CAAC,KAAK,EAAE,EAAE;YACzB,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM;gBAAE,OAAO;YAC5D,IAAI,CAAC,KAAK,CAAC,EAAE;gBAAE,OAAO;YACtB,MAAM,KAAK,GAAG,WAAW,EAAE,CAAC;YAC5B,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;gBACnC,IAAI,MAAM,EAAE,CAAC;oBACX,KAAK,gBAAgB,CACnB,KAAK,CAAC,EAAE,EACR,QAAQ,EACR,EAAE,MAAM,EAAE,SAAS,EAAE,8BAA8B,EAAE,EACrD,EAAE,MAAM,EAAE,IAAI,EAAE,CACjB,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAC;YACnC,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;gBACpC,IAAI,MAAM,EAAE,CAAC;oBACX,KAAK,gBAAgB,CACnB,KAAK,CAAC,EAAE,EACR,SAAS,EACT,EAAE,MAAM,EAAE,SAAS,EAAE,+BAA+B,EAAE,EACtD,EAAE,MAAM,EAAE,IAAI,EAAE,CACjB,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,gEAAgE;IAChE,iEAAiE;IACjE,kEAAkE;IAClE,kEAAkE;IAClE,eAAe,CAAC;QACd,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,YAAY;QAC/C,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,EAAE;QAC7B,KAAK,EAAE,MAAM,CAAC,KAAK;KACpB,CAAC,CAAC;IACH,uBAAuB,EAAE,CAAC;IAE1B,cAAc,EAAE,CAAC;IACjB,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,KAAK,KAAK,CAAC,CAAC;IACvD,KAAK,iBAAiB,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAChD,KAAK,iBAAiB,EAAE,CAAC;AAC3B,CAAC,CAAC,CAAC;AAEH,uEAAuE;AACvE,MAAM,YAAY,GAAG,CAAC,IAAY,EAAiB,EAAE;IACnD,IAAI,CAAC;QACH,iEAAiE;QACjE,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,UAAU,CAE1C,CAAC;QACF,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG,GAAS,EAAE;IACxC,YAAY,GAAG,KAAK,CAAC;AACvB,CAAC,CAAC"}
|
package/lib/mask.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type MaskQuery = () => string[];
|
|
2
|
+
/** Register (or with `null`, clear) the mask query. */
|
|
3
|
+
export declare const registerMaskQuery: (query: MaskQuery | null) => void;
|
|
4
|
+
/** The current mask list; empty when unregistered or throwing. */
|
|
5
|
+
export declare const maskedNativeIds: () => string[];
|
|
6
|
+
export declare const __resetForTests: () => void;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=mask.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mask.d.ts","sourceRoot":"","sources":["../src/mask.ts"],"names":[],"mappings":"AAeA,KAAK,SAAS,GAAG,MAAM,MAAM,EAAE,CAAC;AAIhC,uDAAuD;AACvD,eAAO,MAAM,iBAAiB,UAAW,SAAS,GAAG,IAAI,KAAG,IAE3D,CAAC;AAEF,kEAAkE;AAClE,eAAO,MAAM,eAAe,QAAO,MAAM,EASxC,CAAC;AAEF,eAAO,MAAM,eAAe,QAAO,IAElC,CAAC"}
|