@goliapkg/sentori-react-native 6.2.0 → 6.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/SentoriReactNative.podspec +11 -4
- package/android/build.gradle +21 -1
- package/android/src/main/java-core/com/sentori/Sentori.kt +273 -0
- package/android/src/main/{java → java-core}/com/sentori/SentoriAnrWatchdog.kt +3 -0
- package/android/src/main/java-core/com/sentori/SentoriAttachment.kt +153 -0
- package/android/src/main/java-core/com/sentori/SentoriConfig.kt +61 -0
- package/android/src/main/{java → java-core}/com/sentori/SentoriCrashHandler.kt +22 -0
- package/android/src/main/java-core/com/sentori/SentoriDevice.kt +61 -0
- package/android/src/main/{java → java-core}/com/sentori/SentoriFirebaseMessagingService.kt +18 -0
- package/android/src/main/{java → java-core}/com/sentori/SentoriForegroundActivity.kt +3 -0
- package/android/src/main/java-core/com/sentori/SentoriIdentity.kt +101 -0
- package/android/src/main/{java → java-core}/com/sentori/SentoriMobileVitals.kt +3 -0
- package/android/src/main/{java → java-core}/com/sentori/SentoriNativeExceptionBridge.kt +3 -0
- package/android/src/main/{java → java-core}/com/sentori/SentoriNativeSignals.kt +3 -0
- package/android/src/main/java-core/com/sentori/SentoriNotificationTap.kt +148 -0
- package/android/src/main/java-core/com/sentori/SentoriPendingCrash.kt +154 -0
- package/android/src/main/java-core/com/sentori/SentoriPush.kt +588 -0
- package/android/src/main/{java → java-core}/com/sentori/SentoriPushNotifications.kt +171 -17
- package/android/src/main/{java → java-core}/com/sentori/SentoriReplayCapture.kt +3 -0
- package/android/src/main/java-core/com/sentori/SentoriScope.kt +107 -0
- package/android/src/main/{java → java-core}/com/sentori/SentoriScreenshotCapture.kt +3 -0
- package/android/src/main/java-core/com/sentori/SentoriSignalRing.kt +80 -0
- package/android/src/main/java-core/com/sentori/SentoriTransport.kt +476 -0
- package/android/src/test/java-core/com/sentori/SentoriAttachmentTest.kt +413 -0
- package/android/src/test/{java → java-core}/com/sentori/SentoriCrashHandlerTest.kt +3 -0
- package/android/src/test/java-core/com/sentori/SentoriIdentityTest.kt +107 -0
- package/android/src/test/java-core/com/sentori/SentoriLiveServerTest.kt +149 -0
- package/android/src/test/java-core/com/sentori/SentoriPendingCrashTest.kt +194 -0
- package/android/src/test/java-core/com/sentori/SentoriPushTest.kt +707 -0
- package/android/src/test/{java → java-core}/com/sentori/SentoriScreenshotCaptureTest.kt +3 -0
- package/android/src/test/java-core/com/sentori/SentoriVerbsTest.kt +185 -0
- package/ios/core/SentoriAttachment.swift +129 -0
- package/ios/{SentoriCrashHandler.swift → core/SentoriCrashHandler.swift} +31 -0
- package/ios/{SentoriHangWatchdog.swift → core/SentoriHangWatchdog.swift} +3 -0
- package/ios/{SentoriMobileVitals.swift → core/SentoriMobileVitals.swift} +3 -0
- package/ios/{SentoriNativeExceptionBridge.swift → core/SentoriNativeExceptionBridge.swift} +3 -0
- package/ios/{SentoriPushNotifications.swift → core/SentoriPushNotifications.swift} +50 -0
- package/ios/{SentoriReplayCapture.swift → core/SentoriReplayCapture.swift} +3 -0
- package/ios/{SentoriScreenshotCapture.swift → core/SentoriScreenshotCapture.swift} +14 -0
- package/ios/{SentoriThreadSampler.swift → core/SentoriThreadSampler.swift} +3 -0
- package/lib/push.d.ts.map +1 -1
- package/lib/push.js +51 -1
- package/lib/push.js.map +1 -1
- package/lib/scope.d.ts +6 -0
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +45 -1
- package/lib/scope.js.map +1 -1
- package/lib/transport.js +1 -1
- package/package.json +2 -2
- package/src/__tests__/push.test.ts +99 -1
- package/src/push.ts +49 -1
- package/src/scope.ts +50 -1
- package/src/transport.ts +1 -1
- package/ios/Tests/SentoriCrashHandlerTests.swift +0 -59
- package/ios/Tests/SentoriScreenshotCaptureTests.swift +0 -59
- package/ios/Tests/SentoriThreadSamplerTests.swift +0 -96
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// GENERATED MIRROR — do not edit.
|
|
2
|
+
// Source of truth: sdk/native/android/src/test/java/com/sentori/SentoriPendingCrashTest.kt
|
|
3
|
+
// Run `node scripts/sync-native-core.mjs` after editing it.
|
|
4
|
+
package com.sentori
|
|
5
|
+
|
|
6
|
+
import androidx.test.core.app.ApplicationProvider
|
|
7
|
+
import org.json.JSONObject
|
|
8
|
+
import org.junit.After
|
|
9
|
+
import org.junit.Assert.assertEquals
|
|
10
|
+
import org.junit.Assert.assertNotNull
|
|
11
|
+
import org.junit.Assert.assertTrue
|
|
12
|
+
import org.junit.Before
|
|
13
|
+
import org.junit.Test
|
|
14
|
+
import org.junit.runner.RunWith
|
|
15
|
+
import org.robolectric.RobolectricTestRunner
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The crash that killed the app has to survive the app.
|
|
19
|
+
*
|
|
20
|
+
* [SentoriCrashHandler] wrote one JSON file per crash and nothing in
|
|
21
|
+
* this package read them: the directory filled up and no crash was
|
|
22
|
+
* ever sent.
|
|
23
|
+
*
|
|
24
|
+
* Deliberately the same assertions as `SentoriPendingCrashTests.swift`
|
|
25
|
+
* — a crash must arrive identical whichever SDK sent it.
|
|
26
|
+
*/
|
|
27
|
+
@RunWith(RobolectricTestRunner::class)
|
|
28
|
+
class SentoriPendingCrashTest {
|
|
29
|
+
|
|
30
|
+
private val context get() = ApplicationProvider.getApplicationContext<android.content.Context>()
|
|
31
|
+
|
|
32
|
+
private val onDisk = JSONObject(
|
|
33
|
+
"""
|
|
34
|
+
{
|
|
35
|
+
"id": "019ff080-2aeb-7e30-aba1-4431b296d120",
|
|
36
|
+
"timestamp": "2026-08-11T09:00:00.000Z",
|
|
37
|
+
"kind": "error",
|
|
38
|
+
"platform": "android",
|
|
39
|
+
"release": "com.example@1.4.0+220",
|
|
40
|
+
"environment": "production",
|
|
41
|
+
"device": { "os": "android", "osVersion": "15", "model": "Google Pixel 9" },
|
|
42
|
+
"app": { "version": "1.4.0" },
|
|
43
|
+
"error": {
|
|
44
|
+
"type": "java.lang.IllegalStateException",
|
|
45
|
+
"message": "boom",
|
|
46
|
+
"stack": [
|
|
47
|
+
{ "function": "com.example.Foo.bar", "file": "Foo.kt", "line": 42, "inApp": true },
|
|
48
|
+
{ "function": "main" }
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
""".trimIndent()
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
@Before
|
|
56
|
+
fun setUp() {
|
|
57
|
+
SentoriTransport.resetForTests()
|
|
58
|
+
SentoriConfig.resetForTests()
|
|
59
|
+
// The pending directory is one place on disk shared by every
|
|
60
|
+
// test in the process, and `SentoriCrashHandlerTest` writes
|
|
61
|
+
// there too. Without this one test drains another's crash and
|
|
62
|
+
// asserts on the wrong exception.
|
|
63
|
+
SentoriCrashHandler.register(context)
|
|
64
|
+
SentoriCrashHandler.consumePending()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@After
|
|
68
|
+
fun tearDown() {
|
|
69
|
+
SentoriTransport.resetForTests()
|
|
70
|
+
SentoriConfig.resetForTests()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@Test
|
|
74
|
+
fun everyFieldSurvivesTheConversion() {
|
|
75
|
+
val wire = SentoriPendingCrash.toWire(onDisk)
|
|
76
|
+
|
|
77
|
+
assertEquals("019ff080-2aeb-7e30-aba1-4431b296d120", wire["id"])
|
|
78
|
+
assertEquals("error", wire["kind"])
|
|
79
|
+
assertEquals("android", wire["platform"])
|
|
80
|
+
assertEquals("com.example@1.4.0+220", wire["release"])
|
|
81
|
+
assertEquals("production", wire["environment"])
|
|
82
|
+
|
|
83
|
+
// The file says `timestamp`, the wire says `occurredAt`, and
|
|
84
|
+
// the value is the moment the app died — not the moment the
|
|
85
|
+
// next launch noticed. Losing it puts every crash at the time
|
|
86
|
+
// of the following start-up.
|
|
87
|
+
assertEquals("2026-08-11T09:00:00.000Z", wire["occurredAt"])
|
|
88
|
+
|
|
89
|
+
@Suppress("UNCHECKED_CAST") val payload = wire["payload"] as Map<String, Any?>
|
|
90
|
+
@Suppress("UNCHECKED_CAST") val error = payload["error"] as Map<String, Any?>
|
|
91
|
+
assertEquals("java.lang.IllegalStateException", error["type"])
|
|
92
|
+
assertEquals("boom", error["message"])
|
|
93
|
+
|
|
94
|
+
@Suppress("UNCHECKED_CAST") val stack = error["stack"] as List<Map<String, Any?>>
|
|
95
|
+
assertEquals(2, stack.size)
|
|
96
|
+
assertEquals("com.example.Foo.bar", stack[0]["function"])
|
|
97
|
+
assertEquals(42, stack[0]["line"])
|
|
98
|
+
assertEquals(true, stack[0]["inApp"])
|
|
99
|
+
// A frame with only a function keeps only a function rather
|
|
100
|
+
// than gaining nulls the server would have to ignore.
|
|
101
|
+
assertEquals(1, stack[1].size)
|
|
102
|
+
|
|
103
|
+
@Suppress("UNCHECKED_CAST") val device = payload["device"] as Map<String, Any?>
|
|
104
|
+
assertEquals("Google Pixel 9", device["model"])
|
|
105
|
+
assertEquals(true, payload["nativeCrash"])
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@Test
|
|
109
|
+
fun aMissingErrorStillProducesAReportableEvent() {
|
|
110
|
+
// A crash file written mid-teardown can be missing anything.
|
|
111
|
+
// Dropping the event would lose the only record that the app
|
|
112
|
+
// died at all.
|
|
113
|
+
val wire = SentoriPendingCrash.toWire(JSONObject("""{"platform":"android"}"""))
|
|
114
|
+
@Suppress("UNCHECKED_CAST") val payload = wire["payload"] as Map<String, Any?>
|
|
115
|
+
@Suppress("UNCHECKED_CAST") val error = payload["error"] as Map<String, Any?>
|
|
116
|
+
|
|
117
|
+
assertEquals("NativeCrash", error["type"])
|
|
118
|
+
assertEquals("", error["message"])
|
|
119
|
+
assertEquals(0, (error["stack"] as List<*>).size)
|
|
120
|
+
assertEquals(36, (wire["id"] as String).length)
|
|
121
|
+
assertNotNull(wire["occurredAt"])
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@Test
|
|
125
|
+
fun iosFilesKeepTheirPlatform() {
|
|
126
|
+
// The same converter runs on both, and a crash filed as
|
|
127
|
+
// `android` would symbolicate against the wrong artifact.
|
|
128
|
+
assertEquals(
|
|
129
|
+
"ios",
|
|
130
|
+
SentoriPendingCrash.toWire(JSONObject("""{"platform":"ios"}"""))["platform"],
|
|
131
|
+
)
|
|
132
|
+
// Anything unrecognised is this platform rather than a value
|
|
133
|
+
// the server has no column for.
|
|
134
|
+
assertEquals(
|
|
135
|
+
"android",
|
|
136
|
+
SentoriPendingCrash.toWire(JSONObject("""{"platform":"haiku"}"""))["platform"],
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@Test
|
|
141
|
+
fun theConvertedEventIsEncodable() {
|
|
142
|
+
// It goes through `org.json` on the way out; a value that
|
|
143
|
+
// cannot encode would lose the batch it rides in.
|
|
144
|
+
val json = SentoriTransport.toJson(SentoriPendingCrash.toWire(onDisk)).toString()
|
|
145
|
+
assertTrue(json.contains("nativeCrash"))
|
|
146
|
+
assertTrue(json.contains("com.example.Foo.bar"))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@Test
|
|
150
|
+
fun startIsWhatDrainsThem() {
|
|
151
|
+
// Through `Sentori.start`, not `ship` directly.
|
|
152
|
+
//
|
|
153
|
+
// The iOS suite's first version called `ship()` itself, so
|
|
154
|
+
// deleting the call from `start` left every test green — the
|
|
155
|
+
// same shape as the bug being fixed, one level up. A unit test
|
|
156
|
+
// of a function nobody calls proves the function, not the
|
|
157
|
+
// feature.
|
|
158
|
+
// `persistForTesting` writes the file the way the handler
|
|
159
|
+
// does. Invoking the installed handler directly recurses
|
|
160
|
+
// through it into itself and dies with a StackOverflowError —
|
|
161
|
+
// which is a fact about the test, not about the SDK.
|
|
162
|
+
SentoriCrashHandler.installForTesting(context)
|
|
163
|
+
SentoriCrashHandler.persistForTesting(IllegalStateException("StartupDrain"))
|
|
164
|
+
|
|
165
|
+
Sentori.start(
|
|
166
|
+
SentoriConfig(
|
|
167
|
+
token = "st_test",
|
|
168
|
+
ingestUrl = "http://127.0.0.1:9",
|
|
169
|
+
release = "app@1.0.0",
|
|
170
|
+
environment = "test",
|
|
171
|
+
),
|
|
172
|
+
context,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
val deadline = System.currentTimeMillis() + 10_000
|
|
176
|
+
var found = false
|
|
177
|
+
while (System.currentTimeMillis() < deadline && !found) {
|
|
178
|
+
val all = SentoriTransport.peekQueue() + SentoriTransport.peekPersisted()
|
|
179
|
+
found =
|
|
180
|
+
all.any { ev ->
|
|
181
|
+
@Suppress("UNCHECKED_CAST")
|
|
182
|
+
val p = ev["payload"] as? Map<String, Any?>
|
|
183
|
+
@Suppress("UNCHECKED_CAST")
|
|
184
|
+
val e = p?.get("error") as? Map<String, Any?>
|
|
185
|
+
(e?.get("message") as? String)?.contains("StartupDrain") == true
|
|
186
|
+
}
|
|
187
|
+
if (!found) Thread.sleep(100)
|
|
188
|
+
}
|
|
189
|
+
assertTrue(
|
|
190
|
+
"start did not drain the pending crash — the handler writes files nobody reads",
|
|
191
|
+
found,
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
}
|