@goliapkg/sentori-react-native 0.1.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/sentori/SentoriAnrWatchdog.kt +227 -0
- package/android/src/main/java/com/sentori/SentoriModule.kt +18 -0
- package/lib/breadcrumbs.d.ts +1 -1
- package/lib/breadcrumbs.d.ts.map +1 -1
- package/lib/breadcrumbs.js +20 -15
- package/lib/breadcrumbs.js.map +1 -1
- package/lib/index.d.ts +3 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/native.d.ts +16 -0
- package/lib/native.d.ts.map +1 -1
- package/lib/native.js +26 -0
- package/lib/native.js.map +1 -1
- package/lib/stack.d.ts.map +1 -1
- package/lib/stack.js +6 -68
- package/lib/stack.js.map +1 -1
- package/lib/types.d.ts +1 -61
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +2 -0
- package/lib/types.js.map +1 -1
- package/lib/uuid.d.ts +1 -10
- package/lib/uuid.d.ts.map +1 -1
- package/lib/uuid.js +2 -45
- package/lib/uuid.js.map +1 -1
- package/package.json +5 -2
- package/src/__tests__/stack.test.ts +6 -4
- package/src/breadcrumbs.ts +32 -23
- package/src/index.ts +5 -1
- package/src/native.ts +42 -0
- package/src/stack.ts +8 -70
- package/src/types.ts +18 -63
- package/src/uuid.ts +2 -56
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
package com.sentori
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.os.Build
|
|
5
|
+
import android.os.Handler
|
|
6
|
+
import android.os.Looper
|
|
7
|
+
import org.json.JSONArray
|
|
8
|
+
import org.json.JSONObject
|
|
9
|
+
import java.io.File
|
|
10
|
+
import java.text.SimpleDateFormat
|
|
11
|
+
import java.util.Date
|
|
12
|
+
import java.util.Locale
|
|
13
|
+
import java.util.TimeZone
|
|
14
|
+
import java.util.UUID
|
|
15
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* ANR (Application Not Responding) detector.
|
|
19
|
+
*
|
|
20
|
+
* Lightweight watchdog: a worker thread posts a tick onto the main
|
|
21
|
+
* Looper every `intervalMs` and sleeps for `timeoutMs`. If the main
|
|
22
|
+
* thread didn't acknowledge the tick within that window we capture the
|
|
23
|
+
* main thread's stack trace as a Sentori event with `kind = "anr"`
|
|
24
|
+
* and write it to the same pending dir SentoriCrashHandler uses, so
|
|
25
|
+
* the next-launch drain delivers it.
|
|
26
|
+
*
|
|
27
|
+
* Design notes:
|
|
28
|
+
* - Single-shot per ANR: once we report, we wait for the main
|
|
29
|
+
* thread to recover before re-arming. Otherwise a 30 s freeze
|
|
30
|
+
* would dump six events.
|
|
31
|
+
* - We DON'T kill the process or rethrow. The OS does that on its
|
|
32
|
+
* own ANR threshold (~5 s for input-driven, longer otherwise).
|
|
33
|
+
* - Worker thread is a daemon so it doesn't keep the process alive.
|
|
34
|
+
* - Disabled in debug builds by default — the JS debugger pauses
|
|
35
|
+
* the main thread routinely and we don't want a flood. The host
|
|
36
|
+
* app can override via `SentoriAnrWatchdog.start(ctx, force = true)`.
|
|
37
|
+
*/
|
|
38
|
+
object SentoriAnrWatchdog {
|
|
39
|
+
|
|
40
|
+
private const val DEFAULT_TIMEOUT_MS = 5_000L
|
|
41
|
+
private const val DEFAULT_INTERVAL_MS = 1_000L
|
|
42
|
+
private const val PENDING_DIR_NAME = "sentori/pending"
|
|
43
|
+
|
|
44
|
+
@Volatile private var running = AtomicBoolean(false)
|
|
45
|
+
@Volatile private var thread: Thread? = null
|
|
46
|
+
@Volatile private var appCtx: Context? = null
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Start the watchdog. Idempotent — calling start() twice is a
|
|
50
|
+
* no-op. Pass `force = true` to enable in debug builds.
|
|
51
|
+
*/
|
|
52
|
+
@JvmStatic
|
|
53
|
+
@JvmOverloads
|
|
54
|
+
fun start(
|
|
55
|
+
context: Context,
|
|
56
|
+
timeoutMs: Long = DEFAULT_TIMEOUT_MS,
|
|
57
|
+
intervalMs: Long = DEFAULT_INTERVAL_MS,
|
|
58
|
+
force: Boolean = false,
|
|
59
|
+
) {
|
|
60
|
+
if (!force && isDebuggable(context)) return
|
|
61
|
+
if (running.getAndSet(true)) return
|
|
62
|
+
appCtx = context.applicationContext
|
|
63
|
+
|
|
64
|
+
val mainHandler = Handler(Looper.getMainLooper())
|
|
65
|
+
val watchdogThread = Thread {
|
|
66
|
+
val tick = MainTick()
|
|
67
|
+
while (running.get()) {
|
|
68
|
+
tick.armed = true
|
|
69
|
+
mainHandler.post(tick)
|
|
70
|
+
try {
|
|
71
|
+
Thread.sleep(timeoutMs)
|
|
72
|
+
} catch (_: InterruptedException) {
|
|
73
|
+
return@Thread
|
|
74
|
+
}
|
|
75
|
+
if (tick.armed) {
|
|
76
|
+
// Main thread is wedged — capture once, then wait
|
|
77
|
+
// for the tick to land before arming again.
|
|
78
|
+
captureAnr()
|
|
79
|
+
while (running.get() && tick.armed) {
|
|
80
|
+
try {
|
|
81
|
+
Thread.sleep(intervalMs)
|
|
82
|
+
} catch (_: InterruptedException) {
|
|
83
|
+
return@Thread
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
watchdogThread.name = "Sentori-ANR-Watchdog"
|
|
90
|
+
watchdogThread.isDaemon = true
|
|
91
|
+
watchdogThread.start()
|
|
92
|
+
thread = watchdogThread
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@JvmStatic
|
|
96
|
+
fun stop() {
|
|
97
|
+
running.set(false)
|
|
98
|
+
thread?.interrupt()
|
|
99
|
+
thread = null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private fun captureAnr() {
|
|
103
|
+
val ctx = appCtx ?: return
|
|
104
|
+
try {
|
|
105
|
+
val mainStack = Looper.getMainLooper().thread.stackTrace
|
|
106
|
+
val event = buildAnrEvent(ctx, mainStack)
|
|
107
|
+
val dir = File(ctx.filesDir, PENDING_DIR_NAME)
|
|
108
|
+
if (!dir.exists()) dir.mkdirs()
|
|
109
|
+
val file = File(dir, "${uuid()}.json")
|
|
110
|
+
file.writeText(event.toString())
|
|
111
|
+
} catch (_: Throwable) {
|
|
112
|
+
// never throw from inside the watchdog — losing one
|
|
113
|
+
// capture beats killing the worker thread.
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private fun buildAnrEvent(ctx: Context, mainStack: Array<StackTraceElement>): JSONObject {
|
|
118
|
+
val cfg = configMap(ctx)
|
|
119
|
+
val release = cfg["release"] ?: "unknown"
|
|
120
|
+
val environment = cfg["environment"] ?: "prod"
|
|
121
|
+
|
|
122
|
+
val device = JSONObject().apply {
|
|
123
|
+
put("os", "android")
|
|
124
|
+
put("osVersion", Build.VERSION.RELEASE)
|
|
125
|
+
put("model", "${Build.MANUFACTURER} ${Build.MODEL}")
|
|
126
|
+
}
|
|
127
|
+
val app = JSONObject().apply {
|
|
128
|
+
put("version", appVersion(ctx))
|
|
129
|
+
put("build", appBuild(ctx))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
val frames = JSONArray()
|
|
133
|
+
for (f in mainStack) {
|
|
134
|
+
frames.put(
|
|
135
|
+
JSONObject().apply {
|
|
136
|
+
put("function", "${f.className}.${f.methodName}")
|
|
137
|
+
put("file", f.fileName ?: "<unknown>")
|
|
138
|
+
put("line", f.lineNumber.coerceAtLeast(0))
|
|
139
|
+
put("inApp", isInApp(f.className))
|
|
140
|
+
},
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
val error = JSONObject().apply {
|
|
145
|
+
put("type", "ApplicationNotResponding")
|
|
146
|
+
put("message", "Main thread blocked for ≥ ${DEFAULT_TIMEOUT_MS} ms")
|
|
147
|
+
put("stack", frames)
|
|
148
|
+
put("cause", JSONObject.NULL)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return JSONObject().apply {
|
|
152
|
+
put("id", uuid())
|
|
153
|
+
put("timestamp", iso8601Now())
|
|
154
|
+
put("kind", "anr")
|
|
155
|
+
put("platform", "android")
|
|
156
|
+
put("release", release)
|
|
157
|
+
put("environment", environment)
|
|
158
|
+
put("device", device)
|
|
159
|
+
put("app", app)
|
|
160
|
+
put("user", JSONObject.NULL)
|
|
161
|
+
put("tags", JSONObject().apply { put("source", "sentori.anrWatchdog") })
|
|
162
|
+
put("breadcrumbs", JSONArray())
|
|
163
|
+
put("error", error)
|
|
164
|
+
put("fingerprint", JSONArray())
|
|
165
|
+
put("traceId", JSONObject.NULL)
|
|
166
|
+
put("spanId", JSONObject.NULL)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private fun configMap(ctx: Context): Map<String, String> {
|
|
171
|
+
val prefs = ctx.getSharedPreferences("sentori", Context.MODE_PRIVATE)
|
|
172
|
+
val out = mutableMapOf<String, String>()
|
|
173
|
+
for ((k, v) in prefs.all) if (v is String) out[k] = v
|
|
174
|
+
return out
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private fun isInApp(cls: String): Boolean {
|
|
178
|
+
if (cls.startsWith("android.")) return false
|
|
179
|
+
if (cls.startsWith("androidx.")) return false
|
|
180
|
+
if (cls.startsWith("java.")) return false
|
|
181
|
+
if (cls.startsWith("javax.")) return false
|
|
182
|
+
if (cls.startsWith("kotlin.")) return false
|
|
183
|
+
if (cls.startsWith("kotlinx.")) return false
|
|
184
|
+
if (cls.startsWith("com.facebook.react.")) return false
|
|
185
|
+
if (cls.startsWith("com.android.")) return false
|
|
186
|
+
if (cls.startsWith("dalvik.")) return false
|
|
187
|
+
if (cls.startsWith("sun.")) return false
|
|
188
|
+
return true
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private fun iso8601Now(): String {
|
|
192
|
+
val f = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
|
|
193
|
+
f.timeZone = TimeZone.getTimeZone("UTC")
|
|
194
|
+
return f.format(Date())
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private fun uuid(): String = UUID.randomUUID().toString().lowercase(Locale.US)
|
|
198
|
+
|
|
199
|
+
private fun appVersion(ctx: Context): String =
|
|
200
|
+
try {
|
|
201
|
+
val pi = ctx.packageManager.getPackageInfo(ctx.packageName, 0)
|
|
202
|
+
pi.versionName ?: "0.0.0"
|
|
203
|
+
} catch (_: Throwable) {
|
|
204
|
+
"0.0.0"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private fun appBuild(ctx: Context): String =
|
|
208
|
+
try {
|
|
209
|
+
val pi = ctx.packageManager.getPackageInfo(ctx.packageName, 0)
|
|
210
|
+
pi.longVersionCode.toString()
|
|
211
|
+
} catch (_: Throwable) {
|
|
212
|
+
"0"
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private fun isDebuggable(ctx: Context): Boolean =
|
|
216
|
+
(ctx.applicationInfo.flags and android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE) != 0
|
|
217
|
+
|
|
218
|
+
/** Posted to the main thread every poll. `armed` is flipped to
|
|
219
|
+
* false once it actually runs. */
|
|
220
|
+
private class MainTick : Runnable {
|
|
221
|
+
@Volatile var armed = true
|
|
222
|
+
|
|
223
|
+
override fun run() {
|
|
224
|
+
armed = false
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -8,6 +8,8 @@ import expo.modules.kotlin.modules.ModuleDefinition
|
|
|
8
8
|
* as the iOS module:
|
|
9
9
|
* - setConfig({ token, release, environment })
|
|
10
10
|
* - drainPending() -> List<String> (JSON bodies)
|
|
11
|
+
* - startAnrWatchdog({ timeoutMs?, intervalMs?, force? })
|
|
12
|
+
* - stopAnrWatchdog()
|
|
11
13
|
*/
|
|
12
14
|
class SentoriModule : Module() {
|
|
13
15
|
override fun definition() = ModuleDefinition {
|
|
@@ -26,6 +28,22 @@ class SentoriModule : Module() {
|
|
|
26
28
|
SentoriCrashHandler.consumePending()
|
|
27
29
|
}
|
|
28
30
|
|
|
31
|
+
// Watchdog is opt-in from JS so the host app picks the
|
|
32
|
+
// trade-off — stricter detection vs noise from the Metro
|
|
33
|
+
// debugger pausing the main thread. Pass `force: true` to
|
|
34
|
+
// run in debug builds.
|
|
35
|
+
Function("startAnrWatchdog") { options: Map<String, Any?>? ->
|
|
36
|
+
val ctx = appContext.reactContext ?: return@Function
|
|
37
|
+
val timeoutMs = (options?.get("timeoutMs") as? Number)?.toLong() ?: 5_000L
|
|
38
|
+
val intervalMs = (options?.get("intervalMs") as? Number)?.toLong() ?: 1_000L
|
|
39
|
+
val force = (options?.get("force") as? Boolean) ?: false
|
|
40
|
+
SentoriAnrWatchdog.start(ctx, timeoutMs, intervalMs, force)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
Function("stopAnrWatchdog") {
|
|
44
|
+
SentoriAnrWatchdog.stop()
|
|
45
|
+
}
|
|
46
|
+
|
|
29
47
|
// Dev-only helper — schedules an uncaught RuntimeException after
|
|
30
48
|
// a tick so the JS bridge has time to return; the crash is then
|
|
31
49
|
// captured by SentoriCrashHandler and written to
|
package/lib/breadcrumbs.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Breadcrumb, BreadcrumbType } from './types';
|
|
2
2
|
export type AddBreadcrumbInput = {
|
|
3
|
-
type: BreadcrumbType;
|
|
4
3
|
data: Record<string, unknown>;
|
|
5
4
|
timestamp?: string;
|
|
5
|
+
type: BreadcrumbType;
|
|
6
6
|
};
|
|
7
7
|
export declare const addBreadcrumb: (input: AddBreadcrumbInput) => void;
|
|
8
8
|
export declare const getBreadcrumbs: () => Breadcrumb[];
|
package/lib/breadcrumbs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"breadcrumbs.d.ts","sourceRoot":"","sources":["../src/breadcrumbs.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"breadcrumbs.d.ts","sourceRoot":"","sources":["../src/breadcrumbs.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAEzD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,cAAc,CAAA;CACrB,CAAA;AAID,eAAO,MAAM,aAAa,GAAI,OAAO,kBAAkB,KAAG,IAWzD,CAAA;AAED,eAAO,MAAM,cAAc,QAAO,UAAU,EAAe,CAAA;AAE3D,eAAO,MAAM,gBAAgB,QAAO,IAGnC,CAAA;AAED,eAAO,MAAM,eAAe,QAAO,IAA0B,CAAA"}
|
package/lib/breadcrumbs.js
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Phase 21: ring buffer logic lives in @goliapkg/sentori-core. The
|
|
2
|
+
// public surface here keeps its object-form `addBreadcrumb({ type,
|
|
3
|
+
// data, timestamp? })` so existing callers don't break, and we
|
|
4
|
+
// expose `__resetForTests` for the test suite.
|
|
5
|
+
import { BreadcrumbBuffer, clearBreadcrumbs as clearCore, getBreadcrumbs as getCore, addBreadcrumb as pushCore, } from '@goliapkg/sentori-core';
|
|
6
|
+
const _shadow = new BreadcrumbBuffer();
|
|
3
7
|
export const addBreadcrumb = (input) => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
if (input.timestamp) {
|
|
9
|
+
// Caller wants a specific timestamp — pushCore stamps `now()`, so
|
|
10
|
+
// we go through a private buffer to preserve that field. This path
|
|
11
|
+
// is rarely used (most callers omit timestamp).
|
|
12
|
+
_shadow.push(input.type, input.data);
|
|
13
|
+
const last = _shadow.snapshot().at(-1);
|
|
14
|
+
if (last)
|
|
15
|
+
last.timestamp = input.timestamp;
|
|
16
|
+
return;
|
|
12
17
|
}
|
|
18
|
+
pushCore(input.type, input.data);
|
|
13
19
|
};
|
|
14
|
-
export const getBreadcrumbs = () =>
|
|
20
|
+
export const getBreadcrumbs = () => getCore();
|
|
15
21
|
export const clearBreadcrumbs = () => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
export const __resetForTests = () => {
|
|
19
|
-
_buffer = [];
|
|
22
|
+
clearCore();
|
|
23
|
+
_shadow.clear();
|
|
20
24
|
};
|
|
25
|
+
export const __resetForTests = () => clearBreadcrumbs();
|
|
21
26
|
//# sourceMappingURL=breadcrumbs.js.map
|
package/lib/breadcrumbs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"breadcrumbs.js","sourceRoot":"","sources":["../src/breadcrumbs.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"breadcrumbs.js","sourceRoot":"","sources":["../src/breadcrumbs.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,mEAAmE;AACnE,+DAA+D;AAC/D,+CAA+C;AAC/C,OAAO,EACL,gBAAgB,EAChB,gBAAgB,IAAI,SAAS,EAC7B,cAAc,IAAI,OAAO,EACzB,aAAa,IAAI,QAAQ,GAC1B,MAAM,wBAAwB,CAAA;AAU/B,MAAM,OAAO,GAAG,IAAI,gBAAgB,EAAE,CAAA;AAEtC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAyB,EAAQ,EAAE;IAC/D,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QACpB,kEAAkE;QAClE,mEAAmE;QACnE,gDAAgD;QAChD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;QACpC,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;QACtC,IAAI,IAAI;YAAE,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAA;QAC1C,OAAM;IACR,CAAC;IACD,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;AAClC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,GAAiB,EAAE,CAAC,OAAO,EAAE,CAAA;AAE3D,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAS,EAAE;IACzC,SAAS,EAAE,CAAA;IACX,OAAO,CAAC,KAAK,EAAE,CAAA;AACjB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,GAAS,EAAE,CAAC,gBAAgB,EAAE,CAAA"}
|
package/lib/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { ErrorBoundary } from './error-boundary';
|
|
|
2
2
|
export declare const sentori: {
|
|
3
3
|
init: (options: import("./init").InitOptions) => void;
|
|
4
4
|
addBreadcrumb: (input: import("./breadcrumbs").AddBreadcrumbInput) => void;
|
|
5
|
-
setUser: (user: import("
|
|
6
|
-
getUser: () => import("
|
|
5
|
+
setUser: (user: import("@goliapkg/sentori-core").User | null) => void;
|
|
6
|
+
getUser: () => import("@goliapkg/sentori-core").User | null;
|
|
7
7
|
captureError: (error: Error, extras?: import("./capture").CaptureExtras) => void;
|
|
8
8
|
captureException: (error: Error, extras?: import("./capture").CaptureExtras) => void;
|
|
9
9
|
ErrorBoundary: typeof ErrorBoundary;
|
|
@@ -13,6 +13,6 @@ export { init, init as initSentori } from './init';
|
|
|
13
13
|
export { addBreadcrumb } from './breadcrumbs';
|
|
14
14
|
export { setUser, getUser, captureError, captureException } from './capture';
|
|
15
15
|
export { ErrorBoundary } from './error-boundary';
|
|
16
|
-
export { triggerNativeCrash } from './native';
|
|
16
|
+
export { startAnrWatchdog, stopAnrWatchdog, triggerNativeCrash, } from './native';
|
|
17
17
|
export type { Event, SentoriError, Frame, Breadcrumb, BreadcrumbType, Device, DeviceOS, App, User, Tags, EventKind, Platform, } from './types';
|
|
18
18
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,eAAO,MAAM,OAAO;;;;;;;;CAQnB,CAAC;AAEF,eAAe,OAAO,CAAC;AAEvB,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,eAAO,MAAM,OAAO;;;;;;;;CAQnB,CAAC;AAEF,eAAe,OAAO,CAAC;AAEvB,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,kBAAkB,GACnB,MAAM,UAAU,CAAC;AAElB,YAAY,EACV,KAAK,EACL,YAAY,EACZ,KAAK,EACL,UAAU,EACV,cAAc,EACd,MAAM,EACN,QAAQ,EACR,GAAG,EACH,IAAI,EACJ,IAAI,EACJ,SAAS,EACT,QAAQ,GACT,MAAM,SAAS,CAAC"}
|
package/lib/index.js
CHANGED
|
@@ -16,5 +16,5 @@ export { init, init as initSentori } from './init';
|
|
|
16
16
|
export { addBreadcrumb } from './breadcrumbs';
|
|
17
17
|
export { setUser, getUser, captureError, captureException } from './capture';
|
|
18
18
|
export { ErrorBoundary } from './error-boundary';
|
|
19
|
-
export { triggerNativeCrash } from './native';
|
|
19
|
+
export { startAnrWatchdog, stopAnrWatchdog, triggerNativeCrash, } from './native';
|
|
20
20
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,IAAI;IACJ,aAAa;IACb,OAAO;IACP,OAAO;IACP,YAAY;IACZ,gBAAgB;IAChB,aAAa;CACd,CAAC;AAEF,eAAe,OAAO,CAAC;AAEvB,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,IAAI;IACJ,aAAa;IACb,OAAO;IACP,OAAO;IACP,YAAY;IACZ,gBAAgB;IAChB,aAAa;CACd,CAAC;AAEF,eAAe,OAAO,CAAC;AAEvB,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,kBAAkB,GACnB,MAAM,UAAU,CAAC"}
|
package/lib/native.d.ts
CHANGED
|
@@ -20,4 +20,20 @@ export declare function drainNativePending(): Promise<string[]>;
|
|
|
20
20
|
* No-op when the native module isn't installed (jest, bun test, web).
|
|
21
21
|
*/
|
|
22
22
|
export declare function triggerNativeCrash(): void;
|
|
23
|
+
/**
|
|
24
|
+
* Phase 22 sub-D: start the Android ANR watchdog.
|
|
25
|
+
*
|
|
26
|
+
* startAnrWatchdog() // default 5s/1s, prod-only
|
|
27
|
+
* startAnrWatchdog({ force: true }) // include debug builds
|
|
28
|
+
* startAnrWatchdog({ timeoutMs: 3000 }) // tighter threshold
|
|
29
|
+
*
|
|
30
|
+
* Returns silently on iOS / web / jest. iOS hang detection (sub-E)
|
|
31
|
+
* will hook the same JS function once landed.
|
|
32
|
+
*/
|
|
33
|
+
export declare function startAnrWatchdog(options?: {
|
|
34
|
+
force?: boolean;
|
|
35
|
+
intervalMs?: number;
|
|
36
|
+
timeoutMs?: number;
|
|
37
|
+
}): void;
|
|
38
|
+
export declare function stopAnrWatchdog(): void;
|
|
23
39
|
//# sourceMappingURL=native.d.ts.map
|
package/lib/native.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwCH,wBAAgB,eAAe,CAAC,MAAM,EAAE;IACtC,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;CACd,GAAG,IAAI,CAMP;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAQ5D;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAMzC;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE;IACzC,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,GAAG,IAAI,CAMP;AAED,wBAAgB,eAAe,IAAI,IAAI,CAMtC"}
|
package/lib/native.js
CHANGED
|
@@ -53,4 +53,30 @@ export function triggerNativeCrash() {
|
|
|
53
53
|
// never throw from a debugging helper
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Phase 22 sub-D: start the Android ANR watchdog.
|
|
58
|
+
*
|
|
59
|
+
* startAnrWatchdog() // default 5s/1s, prod-only
|
|
60
|
+
* startAnrWatchdog({ force: true }) // include debug builds
|
|
61
|
+
* startAnrWatchdog({ timeoutMs: 3000 }) // tighter threshold
|
|
62
|
+
*
|
|
63
|
+
* Returns silently on iOS / web / jest. iOS hang detection (sub-E)
|
|
64
|
+
* will hook the same JS function once landed.
|
|
65
|
+
*/
|
|
66
|
+
export function startAnrWatchdog(options) {
|
|
67
|
+
try {
|
|
68
|
+
native()?.startAnrWatchdog?.(options);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// never throw from init helpers
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export function stopAnrWatchdog() {
|
|
75
|
+
try {
|
|
76
|
+
native()?.stopAnrWatchdog?.();
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// ignore
|
|
80
|
+
}
|
|
81
|
+
}
|
|
56
82
|
//# sourceMappingURL=native.js.map
|
package/lib/native.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native.js","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"native.js","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAyBH,IAAI,OAA+C,CAAA;AAEnD,SAAS,MAAM;IACb,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,OAAO,CAAA;IACzC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,OAAO,CAAC,mBAAmB,CAEvC,CAAA;QACD,OAAO,GAAG,IAAI,CAAC,mBAAmB,CAAsB,SAAS,CAAC,CAAA;IACpE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,IAAI,CAAA;IAChB,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAI/B;IACC,IAAI,CAAC;QACH,MAAM,EAAE,EAAE,SAAS,CAAC,MAAM,CAAC,CAAA;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,sBAAsB;IACxB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,MAAM,CAAC,GAAG,MAAM,EAAE,CAAA;IAClB,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,CAAA;IACjB,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,CAAC,YAAY,EAAE,CAAA;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAA;IACX,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB;IAChC,IAAI,CAAC;QACH,MAAM,EAAE,EAAE,sBAAsB,EAAE,EAAE,CAAA;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,sCAAsC;IACxC,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAIhC;IACC,IAAI,CAAC;QACH,MAAM,EAAE,EAAE,gBAAgB,EAAE,CAAC,OAAO,CAAC,CAAA;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,gCAAgC;IAClC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,IAAI,CAAC;QACH,MAAM,EAAE,EAAE,eAAe,EAAE,EAAE,CAAA;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,SAAS;IACX,CAAC;AACH,CAAC"}
|
package/lib/stack.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stack.d.ts","sourceRoot":"","sources":["../src/stack.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stack.d.ts","sourceRoot":"","sources":["../src/stack.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAEpC,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,GAAG,SAAS,KAAG,KAAK,EACrC,CAAA"}
|
package/lib/stack.js
CHANGED
|
@@ -1,69 +1,7 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const AT_FRAME = /^(.+?)@(.+?)(?::(\d+))?(?::(\d+))?$/;
|
|
8
|
-
export const parseStack = (stack) => {
|
|
9
|
-
if (!stack || typeof stack !== 'string')
|
|
10
|
-
return [];
|
|
11
|
-
const lines = stack.split('\n');
|
|
12
|
-
const frames = [];
|
|
13
|
-
for (const raw of lines) {
|
|
14
|
-
const line = raw.trim();
|
|
15
|
-
if (!line)
|
|
16
|
-
continue;
|
|
17
|
-
// Skip the "ErrorType: message" header line.
|
|
18
|
-
if (!line.startsWith('at ') && !line.includes('@'))
|
|
19
|
-
continue;
|
|
20
|
-
const frame = parseV8(line) ?? parseAt(line);
|
|
21
|
-
if (frame)
|
|
22
|
-
frames.push(frame);
|
|
23
|
-
}
|
|
24
|
-
return frames;
|
|
25
|
-
};
|
|
26
|
-
const parseV8 = (line) => {
|
|
27
|
-
if (!line.startsWith('at '))
|
|
28
|
-
return null;
|
|
29
|
-
const m = V8_FRAME.exec(line);
|
|
30
|
-
if (!m)
|
|
31
|
-
return null;
|
|
32
|
-
const fn = m[1] ? m[1].trim() : undefined;
|
|
33
|
-
const file = m[2] ? m[2].trim() : '<anonymous>';
|
|
34
|
-
const lineNo = m[3] ? parseInt(m[3], 10) : 0;
|
|
35
|
-
const col = m[4] ? parseInt(m[4], 10) : undefined;
|
|
36
|
-
return {
|
|
37
|
-
function: fn,
|
|
38
|
-
file,
|
|
39
|
-
line: lineNo,
|
|
40
|
-
column: col,
|
|
41
|
-
inApp: isInApp(file),
|
|
42
|
-
};
|
|
43
|
-
};
|
|
44
|
-
const parseAt = (line) => {
|
|
45
|
-
const m = AT_FRAME.exec(line);
|
|
46
|
-
if (!m)
|
|
47
|
-
return null;
|
|
48
|
-
const fn = m[1] ? m[1].trim() : undefined;
|
|
49
|
-
const file = m[2] ? m[2].trim() : '<anonymous>';
|
|
50
|
-
const lineNo = m[3] ? parseInt(m[3], 10) : 0;
|
|
51
|
-
const col = m[4] ? parseInt(m[4], 10) : undefined;
|
|
52
|
-
return {
|
|
53
|
-
function: fn,
|
|
54
|
-
file,
|
|
55
|
-
line: lineNo,
|
|
56
|
-
column: col,
|
|
57
|
-
inApp: isInApp(file),
|
|
58
|
-
};
|
|
59
|
-
};
|
|
60
|
-
const isInApp = (file) => {
|
|
61
|
-
if (!file || file === '<anonymous>')
|
|
62
|
-
return false;
|
|
63
|
-
if (file.includes('node_modules/'))
|
|
64
|
-
return false;
|
|
65
|
-
if (/^https?:\/\//.test(file))
|
|
66
|
-
return false;
|
|
67
|
-
return true;
|
|
68
|
-
};
|
|
1
|
+
// Phase 21: regex + parse loop moved to @goliapkg/sentori-core.
|
|
2
|
+
// RN keeps the long path (no shortFilenames option) because Hermes
|
|
3
|
+
// paths are already short and native symbolication needs the
|
|
4
|
+
// absolute form.
|
|
5
|
+
import { parseStack as parseStackCore } from '@goliapkg/sentori-core';
|
|
6
|
+
export const parseStack = (stack) => parseStackCore(stack);
|
|
69
7
|
//# sourceMappingURL=stack.js.map
|
package/lib/stack.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stack.js","sourceRoot":"","sources":["../src/stack.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stack.js","sourceRoot":"","sources":["../src/stack.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,mEAAmE;AACnE,6DAA6D;AAC7D,iBAAiB;AACjB,OAAO,EAAE,UAAU,IAAI,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAIrE,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAyB,EAAW,EAAE,CAC/D,cAAc,CAAC,KAAK,CAAC,CAAA"}
|
package/lib/types.d.ts
CHANGED
|
@@ -1,62 +1,2 @@
|
|
|
1
|
-
export type Platform
|
|
2
|
-
export type DeviceOS = 'ios' | 'android' | 'web' | 'other';
|
|
3
|
-
export type EventKind = 'error';
|
|
4
|
-
export type BreadcrumbType = 'nav' | 'net' | 'log' | 'user' | 'custom';
|
|
5
|
-
export type Event = {
|
|
6
|
-
id: string;
|
|
7
|
-
timestamp: string;
|
|
8
|
-
kind: EventKind;
|
|
9
|
-
platform: Platform;
|
|
10
|
-
release: string;
|
|
11
|
-
environment: string;
|
|
12
|
-
device: Device;
|
|
13
|
-
app: App;
|
|
14
|
-
user?: User | null;
|
|
15
|
-
tags?: Tags;
|
|
16
|
-
breadcrumbs?: Breadcrumb[];
|
|
17
|
-
error: SentoriError;
|
|
18
|
-
fingerprint?: string[];
|
|
19
|
-
traceId?: string | null;
|
|
20
|
-
spanId?: string | null;
|
|
21
|
-
};
|
|
22
|
-
export type Device = {
|
|
23
|
-
os: DeviceOS;
|
|
24
|
-
osVersion: string;
|
|
25
|
-
model?: string;
|
|
26
|
-
locale?: string;
|
|
27
|
-
};
|
|
28
|
-
export type App = {
|
|
29
|
-
version: string;
|
|
30
|
-
build?: string;
|
|
31
|
-
framework?: {
|
|
32
|
-
name: string;
|
|
33
|
-
version: string;
|
|
34
|
-
};
|
|
35
|
-
};
|
|
36
|
-
export type User = {
|
|
37
|
-
id?: string;
|
|
38
|
-
anonymous?: boolean;
|
|
39
|
-
};
|
|
40
|
-
export type Tags = Record<string, string>;
|
|
41
|
-
export type SentoriError = {
|
|
42
|
-
type: string;
|
|
43
|
-
message: string;
|
|
44
|
-
stack: Frame[];
|
|
45
|
-
cause?: SentoriError | null;
|
|
46
|
-
};
|
|
47
|
-
export type Frame = {
|
|
48
|
-
function?: string;
|
|
49
|
-
file: string;
|
|
50
|
-
line: number;
|
|
51
|
-
column?: number;
|
|
52
|
-
inApp: boolean;
|
|
53
|
-
absolutePath?: string;
|
|
54
|
-
preContext?: string[];
|
|
55
|
-
postContext?: string[];
|
|
56
|
-
};
|
|
57
|
-
export type Breadcrumb = {
|
|
58
|
-
timestamp: string;
|
|
59
|
-
type: BreadcrumbType;
|
|
60
|
-
data: Record<string, unknown>;
|
|
61
|
-
};
|
|
1
|
+
export type { App, Breadcrumb, BreadcrumbType, CaptureExtras, Device, DeviceOS, Event, EventKind, Frame, Platform, SentoriError, Tags, User, } from '@goliapkg/sentori-core';
|
|
62
2
|
//# sourceMappingURL=types.d.ts.map
|
package/lib/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA,YAAY,EACV,GAAG,EACH,UAAU,EACV,cAAc,EACd,aAAa,EACb,MAAM,EACN,QAAQ,EACR,KAAK,EACL,SAAS,EACT,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,IAAI,EACJ,IAAI,GACL,MAAM,wBAAwB,CAAA"}
|
package/lib/types.js
CHANGED
package/lib/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,2DAA2D"}
|
package/lib/uuid.d.ts
CHANGED
|
@@ -1,11 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
* RFC 9562 UUID v7 generator.
|
|
3
|
-
* Layout:
|
|
4
|
-
* bytes 0-5 (48 bits) — Unix epoch milliseconds, big-endian
|
|
5
|
-
* byte 6 (high nibble) — version 7 (0x70)
|
|
6
|
-
* byte 6 (low nibble) + byte 7 — 12 random bits
|
|
7
|
-
* byte 8 (high 2 bits) — variant 10
|
|
8
|
-
* byte 8 (low 6 bits) + bytes 9-15 — 62 random bits
|
|
9
|
-
*/
|
|
10
|
-
export declare const uuidV7: () => string;
|
|
1
|
+
export { uuidV7 } from '@goliapkg/sentori-core';
|
|
11
2
|
//# sourceMappingURL=uuid.d.ts.map
|
package/lib/uuid.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uuid.d.ts","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"uuid.d.ts","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA"}
|
package/lib/uuid.js
CHANGED
|
@@ -1,46 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* Layout:
|
|
4
|
-
* bytes 0-5 (48 bits) — Unix epoch milliseconds, big-endian
|
|
5
|
-
* byte 6 (high nibble) — version 7 (0x70)
|
|
6
|
-
* byte 6 (low nibble) + byte 7 — 12 random bits
|
|
7
|
-
* byte 8 (high 2 bits) — variant 10
|
|
8
|
-
* byte 8 (low 6 bits) + bytes 9-15 — 62 random bits
|
|
9
|
-
*/
|
|
10
|
-
export const uuidV7 = () => {
|
|
11
|
-
const ts = Date.now();
|
|
12
|
-
const buf = new Uint8Array(16);
|
|
13
|
-
buf[0] = Math.floor(ts / 0x10000000000) & 0xff;
|
|
14
|
-
buf[1] = Math.floor(ts / 0x100000000) & 0xff;
|
|
15
|
-
buf[2] = Math.floor(ts / 0x1000000) & 0xff;
|
|
16
|
-
buf[3] = Math.floor(ts / 0x10000) & 0xff;
|
|
17
|
-
buf[4] = Math.floor(ts / 0x100) & 0xff;
|
|
18
|
-
buf[5] = ts & 0xff;
|
|
19
|
-
fillRandom(buf.subarray(6));
|
|
20
|
-
buf[6] = (buf[6] & 0x0f) | 0x70; // version 7
|
|
21
|
-
buf[8] = (buf[8] & 0x3f) | 0x80; // variant 10xx
|
|
22
|
-
const hex = Array.from(buf, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
23
|
-
return (hex.slice(0, 8) +
|
|
24
|
-
'-' +
|
|
25
|
-
hex.slice(8, 12) +
|
|
26
|
-
'-' +
|
|
27
|
-
hex.slice(12, 16) +
|
|
28
|
-
'-' +
|
|
29
|
-
hex.slice(16, 20) +
|
|
30
|
-
'-' +
|
|
31
|
-
hex.slice(20, 32));
|
|
32
|
-
};
|
|
33
|
-
const fillRandom = (buf) => {
|
|
34
|
-
// Prefer crypto.getRandomValues (Hermes 0.74+, browsers, Node, Bun).
|
|
35
|
-
// RN apps targeting older Hermes should add `react-native-get-random-values`
|
|
36
|
-
// before importing the SDK.
|
|
37
|
-
const cryptoObj = globalThis.crypto;
|
|
38
|
-
if (cryptoObj && typeof cryptoObj.getRandomValues === 'function') {
|
|
39
|
-
cryptoObj.getRandomValues(buf);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
for (let i = 0; i < buf.length; i++) {
|
|
43
|
-
buf[i] = Math.floor(Math.random() * 256);
|
|
44
|
-
}
|
|
45
|
-
};
|
|
1
|
+
// Phase 21: moved to @goliapkg/sentori-core.
|
|
2
|
+
export { uuidV7 } from '@goliapkg/sentori-core';
|
|
46
3
|
//# sourceMappingURL=uuid.js.map
|
package/lib/uuid.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uuid.js","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"uuid.js","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goliapkg/sentori-react-native",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Sentori SDK for React Native
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Sentori SDK for React Native \u2014 JS-layer error capture, native crash handlers (iOS / Android), batched transport.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://sentori.golia.jp",
|
|
7
7
|
"repository": {
|
|
@@ -62,5 +62,8 @@
|
|
|
62
62
|
},
|
|
63
63
|
"publishConfig": {
|
|
64
64
|
"access": "public"
|
|
65
|
+
},
|
|
66
|
+
"dependencies": {
|
|
67
|
+
"@goliapkg/sentori-core": "0.2.0"
|
|
65
68
|
}
|
|
66
69
|
}
|
|
@@ -17,12 +17,14 @@ describe('parseStack', () => {
|
|
|
17
17
|
at onPress (src/components/Button.tsx:15:5)`;
|
|
18
18
|
const frames = parseStack(stack);
|
|
19
19
|
expect(frames).toHaveLength(2);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
line: 42,
|
|
20
|
+
// Phase 21: core's parseStack also populates absolutePath, so we
|
|
21
|
+
// match the subset of fields rather than full equality.
|
|
22
|
+
expect(frames[0]).toMatchObject({
|
|
24
23
|
column: 10,
|
|
24
|
+
file: 'src/screens/Checkout.tsx',
|
|
25
|
+
function: 'handleSubmit',
|
|
25
26
|
inApp: true,
|
|
27
|
+
line: 42,
|
|
26
28
|
});
|
|
27
29
|
expect(frames[1]?.function).toBe('onPress');
|
|
28
30
|
});
|
package/src/breadcrumbs.ts
CHANGED
|
@@ -1,33 +1,42 @@
|
|
|
1
|
-
|
|
1
|
+
// Phase 21: ring buffer logic lives in @goliapkg/sentori-core. The
|
|
2
|
+
// public surface here keeps its object-form `addBreadcrumb({ type,
|
|
3
|
+
// data, timestamp? })` so existing callers don't break, and we
|
|
4
|
+
// expose `__resetForTests` for the test suite.
|
|
5
|
+
import {
|
|
6
|
+
BreadcrumbBuffer,
|
|
7
|
+
clearBreadcrumbs as clearCore,
|
|
8
|
+
getBreadcrumbs as getCore,
|
|
9
|
+
addBreadcrumb as pushCore,
|
|
10
|
+
} from '@goliapkg/sentori-core'
|
|
2
11
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
let _buffer: Breadcrumb[] = [];
|
|
12
|
+
import type { Breadcrumb, BreadcrumbType } from './types'
|
|
6
13
|
|
|
7
14
|
export type AddBreadcrumbInput = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
15
|
+
data: Record<string, unknown>
|
|
16
|
+
timestamp?: string
|
|
17
|
+
type: BreadcrumbType
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const _shadow = new BreadcrumbBuffer()
|
|
12
21
|
|
|
13
22
|
export const addBreadcrumb = (input: AddBreadcrumbInput): void => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
if (input.timestamp) {
|
|
24
|
+
// Caller wants a specific timestamp — pushCore stamps `now()`, so
|
|
25
|
+
// we go through a private buffer to preserve that field. This path
|
|
26
|
+
// is rarely used (most callers omit timestamp).
|
|
27
|
+
_shadow.push(input.type, input.data)
|
|
28
|
+
const last = _shadow.snapshot().at(-1)
|
|
29
|
+
if (last) last.timestamp = input.timestamp
|
|
30
|
+
return
|
|
22
31
|
}
|
|
23
|
-
|
|
32
|
+
pushCore(input.type, input.data)
|
|
33
|
+
}
|
|
24
34
|
|
|
25
|
-
export const getBreadcrumbs = (): Breadcrumb[] =>
|
|
35
|
+
export const getBreadcrumbs = (): Breadcrumb[] => getCore()
|
|
26
36
|
|
|
27
37
|
export const clearBreadcrumbs = (): void => {
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
clearCore()
|
|
39
|
+
_shadow.clear()
|
|
40
|
+
}
|
|
30
41
|
|
|
31
|
-
export const __resetForTests = (): void =>
|
|
32
|
-
_buffer = [];
|
|
33
|
-
};
|
|
42
|
+
export const __resetForTests = (): void => clearBreadcrumbs()
|
package/src/index.ts
CHANGED
|
@@ -19,7 +19,11 @@ export { init, init as initSentori } from './init';
|
|
|
19
19
|
export { addBreadcrumb } from './breadcrumbs';
|
|
20
20
|
export { setUser, getUser, captureError, captureException } from './capture';
|
|
21
21
|
export { ErrorBoundary } from './error-boundary';
|
|
22
|
-
export {
|
|
22
|
+
export {
|
|
23
|
+
startAnrWatchdog,
|
|
24
|
+
stopAnrWatchdog,
|
|
25
|
+
triggerNativeCrash,
|
|
26
|
+
} from './native';
|
|
23
27
|
|
|
24
28
|
export type {
|
|
25
29
|
Event,
|
package/src/native.ts
CHANGED
|
@@ -11,6 +11,18 @@ type SentoriNativeModule = {
|
|
|
11
11
|
release: string
|
|
12
12
|
token: string
|
|
13
13
|
}) => void
|
|
14
|
+
/**
|
|
15
|
+
* Phase 22 sub-D: opt-in Android ANR watchdog. Posts a tick to the
|
|
16
|
+
* main looper every `intervalMs`; if not acknowledged within
|
|
17
|
+
* `timeoutMs`, captures the main-thread stack as an `anr` event.
|
|
18
|
+
* No-op on iOS today — iOS hang detection lands in sub-E.
|
|
19
|
+
*/
|
|
20
|
+
startAnrWatchdog?: (options?: {
|
|
21
|
+
force?: boolean
|
|
22
|
+
intervalMs?: number
|
|
23
|
+
timeoutMs?: number
|
|
24
|
+
}) => void
|
|
25
|
+
stopAnrWatchdog?: () => void
|
|
14
26
|
/** Dev-only — example app uses this to verify the crash flow. */
|
|
15
27
|
triggerTestNativeCrash?: () => void
|
|
16
28
|
}
|
|
@@ -69,3 +81,33 @@ export function triggerNativeCrash(): void {
|
|
|
69
81
|
// never throw from a debugging helper
|
|
70
82
|
}
|
|
71
83
|
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Phase 22 sub-D: start the Android ANR watchdog.
|
|
87
|
+
*
|
|
88
|
+
* startAnrWatchdog() // default 5s/1s, prod-only
|
|
89
|
+
* startAnrWatchdog({ force: true }) // include debug builds
|
|
90
|
+
* startAnrWatchdog({ timeoutMs: 3000 }) // tighter threshold
|
|
91
|
+
*
|
|
92
|
+
* Returns silently on iOS / web / jest. iOS hang detection (sub-E)
|
|
93
|
+
* will hook the same JS function once landed.
|
|
94
|
+
*/
|
|
95
|
+
export function startAnrWatchdog(options?: {
|
|
96
|
+
force?: boolean
|
|
97
|
+
intervalMs?: number
|
|
98
|
+
timeoutMs?: number
|
|
99
|
+
}): void {
|
|
100
|
+
try {
|
|
101
|
+
native()?.startAnrWatchdog?.(options)
|
|
102
|
+
} catch {
|
|
103
|
+
// never throw from init helpers
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function stopAnrWatchdog(): void {
|
|
108
|
+
try {
|
|
109
|
+
native()?.stopAnrWatchdog?.()
|
|
110
|
+
} catch {
|
|
111
|
+
// ignore
|
|
112
|
+
}
|
|
113
|
+
}
|
package/src/stack.ts
CHANGED
|
@@ -1,72 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
// Phase 21: regex + parse loop moved to @goliapkg/sentori-core.
|
|
2
|
+
// RN keeps the long path (no shortFilenames option) because Hermes
|
|
3
|
+
// paths are already short and native symbolication needs the
|
|
4
|
+
// absolute form.
|
|
5
|
+
import { parseStack as parseStackCore } from '@goliapkg/sentori-core'
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
// " at functionName (file:line:col)"
|
|
5
|
-
// " at file:line:col"
|
|
6
|
-
const V8_FRAME = /^\s*at\s+(?:(.+?)\s+\()?(.+?)(?::(\d+))?(?::(\d+))?\)?\s*$/;
|
|
7
|
+
import type { Frame } from './types'
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const AT_FRAME = /^(.+?)@(.+?)(?::(\d+))?(?::(\d+))?$/;
|
|
11
|
-
|
|
12
|
-
export const parseStack = (stack: string | undefined): Frame[] => {
|
|
13
|
-
if (!stack || typeof stack !== 'string') return [];
|
|
14
|
-
const lines = stack.split('\n');
|
|
15
|
-
const frames: Frame[] = [];
|
|
16
|
-
|
|
17
|
-
for (const raw of lines) {
|
|
18
|
-
const line = raw.trim();
|
|
19
|
-
if (!line) continue;
|
|
20
|
-
// Skip the "ErrorType: message" header line.
|
|
21
|
-
if (!line.startsWith('at ') && !line.includes('@')) continue;
|
|
22
|
-
|
|
23
|
-
const frame = parseV8(line) ?? parseAt(line);
|
|
24
|
-
if (frame) frames.push(frame);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return frames;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const parseV8 = (line: string): Frame | null => {
|
|
31
|
-
if (!line.startsWith('at ')) return null;
|
|
32
|
-
const m = V8_FRAME.exec(line);
|
|
33
|
-
if (!m) return null;
|
|
34
|
-
|
|
35
|
-
const fn = m[1] ? m[1].trim() : undefined;
|
|
36
|
-
const file = m[2] ? m[2].trim() : '<anonymous>';
|
|
37
|
-
const lineNo = m[3] ? parseInt(m[3], 10) : 0;
|
|
38
|
-
const col = m[4] ? parseInt(m[4], 10) : undefined;
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
function: fn,
|
|
42
|
-
file,
|
|
43
|
-
line: lineNo,
|
|
44
|
-
column: col,
|
|
45
|
-
inApp: isInApp(file),
|
|
46
|
-
};
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const parseAt = (line: string): Frame | null => {
|
|
50
|
-
const m = AT_FRAME.exec(line);
|
|
51
|
-
if (!m) return null;
|
|
52
|
-
|
|
53
|
-
const fn = m[1] ? m[1].trim() : undefined;
|
|
54
|
-
const file = m[2] ? m[2].trim() : '<anonymous>';
|
|
55
|
-
const lineNo = m[3] ? parseInt(m[3], 10) : 0;
|
|
56
|
-
const col = m[4] ? parseInt(m[4], 10) : undefined;
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
function: fn,
|
|
60
|
-
file,
|
|
61
|
-
line: lineNo,
|
|
62
|
-
column: col,
|
|
63
|
-
inApp: isInApp(file),
|
|
64
|
-
};
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const isInApp = (file: string): boolean => {
|
|
68
|
-
if (!file || file === '<anonymous>') return false;
|
|
69
|
-
if (file.includes('node_modules/')) return false;
|
|
70
|
-
if (/^https?:\/\//.test(file)) return false;
|
|
71
|
-
return true;
|
|
72
|
-
};
|
|
9
|
+
export const parseStack = (stack: string | undefined): Frame[] =>
|
|
10
|
+
parseStackCore(stack)
|
package/src/types.ts
CHANGED
|
@@ -1,63 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export type
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
fingerprint?: string[];
|
|
20
|
-
traceId?: string | null;
|
|
21
|
-
spanId?: string | null;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export type Device = {
|
|
25
|
-
os: DeviceOS;
|
|
26
|
-
osVersion: string;
|
|
27
|
-
model?: string;
|
|
28
|
-
locale?: string;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export type App = {
|
|
32
|
-
version: string;
|
|
33
|
-
build?: string;
|
|
34
|
-
framework?: { name: string; version: string };
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export type User = { id?: string; anonymous?: boolean };
|
|
38
|
-
|
|
39
|
-
export type Tags = Record<string, string>;
|
|
40
|
-
|
|
41
|
-
export type SentoriError = {
|
|
42
|
-
type: string;
|
|
43
|
-
message: string;
|
|
44
|
-
stack: Frame[];
|
|
45
|
-
cause?: SentoriError | null;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export type Frame = {
|
|
49
|
-
function?: string;
|
|
50
|
-
file: string;
|
|
51
|
-
line: number;
|
|
52
|
-
column?: number;
|
|
53
|
-
inApp: boolean;
|
|
54
|
-
absolutePath?: string;
|
|
55
|
-
preContext?: string[];
|
|
56
|
-
postContext?: string[];
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
export type Breadcrumb = {
|
|
60
|
-
timestamp: string;
|
|
61
|
-
type: BreadcrumbType;
|
|
62
|
-
data: Record<string, unknown>;
|
|
63
|
-
};
|
|
1
|
+
// Phase 21: wire-format types moved to @goliapkg/sentori-core. Re-
|
|
2
|
+
// exported here so existing relative imports keep working.
|
|
3
|
+
|
|
4
|
+
export type {
|
|
5
|
+
App,
|
|
6
|
+
Breadcrumb,
|
|
7
|
+
BreadcrumbType,
|
|
8
|
+
CaptureExtras,
|
|
9
|
+
Device,
|
|
10
|
+
DeviceOS,
|
|
11
|
+
Event,
|
|
12
|
+
EventKind,
|
|
13
|
+
Frame,
|
|
14
|
+
Platform,
|
|
15
|
+
SentoriError,
|
|
16
|
+
Tags,
|
|
17
|
+
User,
|
|
18
|
+
} from '@goliapkg/sentori-core'
|
package/src/uuid.ts
CHANGED
|
@@ -1,56 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* Layout:
|
|
4
|
-
* bytes 0-5 (48 bits) — Unix epoch milliseconds, big-endian
|
|
5
|
-
* byte 6 (high nibble) — version 7 (0x70)
|
|
6
|
-
* byte 6 (low nibble) + byte 7 — 12 random bits
|
|
7
|
-
* byte 8 (high 2 bits) — variant 10
|
|
8
|
-
* byte 8 (low 6 bits) + bytes 9-15 — 62 random bits
|
|
9
|
-
*/
|
|
10
|
-
export const uuidV7 = (): string => {
|
|
11
|
-
const ts = Date.now();
|
|
12
|
-
const buf = new Uint8Array(16);
|
|
13
|
-
|
|
14
|
-
buf[0] = Math.floor(ts / 0x10000000000) & 0xff;
|
|
15
|
-
buf[1] = Math.floor(ts / 0x100000000) & 0xff;
|
|
16
|
-
buf[2] = Math.floor(ts / 0x1000000) & 0xff;
|
|
17
|
-
buf[3] = Math.floor(ts / 0x10000) & 0xff;
|
|
18
|
-
buf[4] = Math.floor(ts / 0x100) & 0xff;
|
|
19
|
-
buf[5] = ts & 0xff;
|
|
20
|
-
|
|
21
|
-
fillRandom(buf.subarray(6));
|
|
22
|
-
|
|
23
|
-
buf[6] = (buf[6]! & 0x0f) | 0x70; // version 7
|
|
24
|
-
buf[8] = (buf[8]! & 0x3f) | 0x80; // variant 10xx
|
|
25
|
-
|
|
26
|
-
const hex = Array.from(buf, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
27
|
-
return (
|
|
28
|
-
hex.slice(0, 8) +
|
|
29
|
-
'-' +
|
|
30
|
-
hex.slice(8, 12) +
|
|
31
|
-
'-' +
|
|
32
|
-
hex.slice(12, 16) +
|
|
33
|
-
'-' +
|
|
34
|
-
hex.slice(16, 20) +
|
|
35
|
-
'-' +
|
|
36
|
-
hex.slice(20, 32)
|
|
37
|
-
);
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const fillRandom = (buf: Uint8Array): void => {
|
|
41
|
-
// Prefer crypto.getRandomValues (Hermes 0.74+, browsers, Node, Bun).
|
|
42
|
-
// RN apps targeting older Hermes should add `react-native-get-random-values`
|
|
43
|
-
// before importing the SDK.
|
|
44
|
-
const cryptoObj = (
|
|
45
|
-
globalThis as {
|
|
46
|
-
crypto?: { getRandomValues?: (b: Uint8Array) => Uint8Array };
|
|
47
|
-
}
|
|
48
|
-
).crypto;
|
|
49
|
-
if (cryptoObj && typeof cryptoObj.getRandomValues === 'function') {
|
|
50
|
-
cryptoObj.getRandomValues(buf);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
for (let i = 0; i < buf.length; i++) {
|
|
54
|
-
buf[i] = Math.floor(Math.random() * 256);
|
|
55
|
-
}
|
|
56
|
-
};
|
|
1
|
+
// Phase 21: moved to @goliapkg/sentori-core.
|
|
2
|
+
export { uuidV7 } from '@goliapkg/sentori-core'
|