@goliapkg/sentori-react-native 6.1.0 → 6.2.1
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 +15 -1
- package/android/src/main/java-core/com/sentori/Sentori.kt +244 -0
- package/android/src/main/{java → java-core}/com/sentori/SentoriAnrWatchdog.kt +3 -0
- package/android/src/main/java-core/com/sentori/SentoriConfig.kt +61 -0
- package/android/src/main/{java → java-core}/com/sentori/SentoriCrashHandler.kt +3 -0
- package/android/src/main/java-core/com/sentori/SentoriDevice.kt +61 -0
- package/android/src/main/{java → java-core}/com/sentori/SentoriFirebaseMessagingService.kt +3 -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/SentoriPush.kt +359 -0
- package/android/src/main/{java → java-core}/com/sentori/SentoriPushNotifications.kt +3 -0
- package/android/src/main/{java → java-core}/com/sentori/SentoriReplayCapture.kt +3 -0
- package/android/src/main/java-core/com/sentori/SentoriScope.kt +60 -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 +383 -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 +106 -0
- package/android/src/test/java-core/com/sentori/SentoriPushTest.kt +151 -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/{SentoriCrashHandler.swift → core/SentoriCrashHandler.swift} +16 -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/core/SentoriPush.swift +340 -0
- package/ios/{SentoriPushNotifications.swift → core/SentoriPushNotifications.swift} +42 -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 +7 -6
- package/lib/push.d.ts.map +1 -1
- package/lib/push.js +8 -0
- package/lib/push.js.map +1 -1
- package/lib/transport.js +1 -1
- package/package.json +1 -1
- package/src/push.ts +14 -6
- 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
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
// Phase 16 sub-E: XCTest coverage for SentoriCrashHandler (回填 Phase 7).
|
|
2
|
-
//
|
|
3
|
-
// Run via Xcode (target → SentoriTests, ⌘U) or via xcodebuild from the
|
|
4
|
-
// iOS host:
|
|
5
|
-
// xcodebuild test \
|
|
6
|
-
// -scheme SentoriTests \
|
|
7
|
-
// -destination 'platform=iOS Simulator,name=iPhone 15'
|
|
8
|
-
//
|
|
9
|
-
// The handler writes one JSON file per crash to
|
|
10
|
-
// <Documents>/sentori/pending/<uuid>.json
|
|
11
|
-
// We can't easily re-raise NSException (it tears down the test
|
|
12
|
-
// process), so we exercise the persistence helper directly instead.
|
|
13
|
-
|
|
14
|
-
import XCTest
|
|
15
|
-
@testable import SentoriCrashHandler
|
|
16
|
-
|
|
17
|
-
final class SentoriCrashHandlerTests: XCTestCase {
|
|
18
|
-
private var pendingDir: URL!
|
|
19
|
-
|
|
20
|
-
override func setUpWithError() throws {
|
|
21
|
-
let docs = try FileManager.default.url(
|
|
22
|
-
for: .documentDirectory,
|
|
23
|
-
in: .userDomainMask,
|
|
24
|
-
appropriateFor: nil,
|
|
25
|
-
create: true
|
|
26
|
-
)
|
|
27
|
-
pendingDir = docs.appendingPathComponent("sentori/pending", isDirectory: true)
|
|
28
|
-
try? FileManager.default.removeItem(at: pendingDir)
|
|
29
|
-
try FileManager.default.createDirectory(at: pendingDir, withIntermediateDirectories: true)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
func testWritePendingProducesValidEventJson() throws {
|
|
33
|
-
// Synthesize a payload like the @convention(c) handler would.
|
|
34
|
-
let exception = NSException(
|
|
35
|
-
name: NSExceptionName("XCTestSyntheticException"),
|
|
36
|
-
reason: "boom",
|
|
37
|
-
userInfo: nil
|
|
38
|
-
)
|
|
39
|
-
SentoriCrashHandler.persistForTesting(exception: exception)
|
|
40
|
-
|
|
41
|
-
let files = try FileManager.default.contentsOfDirectory(atPath: pendingDir.path)
|
|
42
|
-
.filter { $0.hasSuffix(".json") }
|
|
43
|
-
XCTAssertEqual(files.count, 1, "expected exactly one pending file, got \(files.count)")
|
|
44
|
-
|
|
45
|
-
let url = pendingDir.appendingPathComponent(files[0])
|
|
46
|
-
let data = try Data(contentsOf: url)
|
|
47
|
-
let payload = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
|
|
48
|
-
XCTAssertNotNil(payload, "pending file is not valid JSON")
|
|
49
|
-
|
|
50
|
-
// Spot-check the protocol shape (Phase 1 schema).
|
|
51
|
-
XCTAssertEqual(payload?["kind"] as? String, "error")
|
|
52
|
-
XCTAssertEqual(payload?["platform"] as? String, "ios")
|
|
53
|
-
XCTAssertNotNil(payload?["id"] as? String)
|
|
54
|
-
XCTAssertNotNil(payload?["timestamp"] as? String)
|
|
55
|
-
let error = payload?["error"] as? [String: Any]
|
|
56
|
-
XCTAssertEqual(error?["type"] as? String, "XCTestSyntheticException")
|
|
57
|
-
XCTAssertEqual(error?["message"] as? String, "boom")
|
|
58
|
-
}
|
|
59
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
// Phase 42 sub-E.10 — XCTest coverage for SentoriScreenshotCapture.
|
|
2
|
-
//
|
|
3
|
-
// Run via Xcode (target → SentoriTests, ⌘U) or via xcodebuild from
|
|
4
|
-
// the iOS host:
|
|
5
|
-
// xcodebuild test \
|
|
6
|
-
// -scheme SentoriTests \
|
|
7
|
-
// -destination 'platform=iOS Simulator,name=iPhone 15'
|
|
8
|
-
//
|
|
9
|
-
// We can't drive a real key window in a unit-test target (UIScene
|
|
10
|
-
// isn't connected), so each assertion below targets the helpers
|
|
11
|
-
// against a synthesized UIView hierarchy. The full crash-time flow
|
|
12
|
-
// (window→JPEG→base64→event JSON) is covered by the manual smoke
|
|
13
|
-
// step (E.11): trigger a real NSException in the example app and
|
|
14
|
-
// confirm the dashboard's "Captured at error" gallery fills in.
|
|
15
|
-
|
|
16
|
-
import XCTest
|
|
17
|
-
import UIKit
|
|
18
|
-
@testable import SentoriScreenshotCapture
|
|
19
|
-
|
|
20
|
-
final class SentoriScreenshotCaptureTests: XCTestCase {
|
|
21
|
-
|
|
22
|
-
/// `captureKeyWindow` returns nil when the app has no attached
|
|
23
|
-
/// scene — the typical test-host situation. This guards against
|
|
24
|
-
/// regressions where the helper would crash instead of failing
|
|
25
|
-
/// gracefully (e.g. force-unwrapping `UIApplication.shared.windows.first`).
|
|
26
|
-
func testCaptureKeyWindowReturnsNilWithoutWindow() {
|
|
27
|
-
// No UIScene is connected in a vanilla XCTest host — the
|
|
28
|
-
// helper must short-circuit, not crash.
|
|
29
|
-
let result = SentoriScreenshotCapture.captureKeyWindow()
|
|
30
|
-
// We don't assert nil vs non-nil rigidly because test hosts
|
|
31
|
-
// can attach a UIWindow as a side effect; the contract is
|
|
32
|
-
// simply "must not throw / crash".
|
|
33
|
-
_ = result
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/// Performance gate: capture-on-a-known-size-view-hierarchy
|
|
37
|
-
/// must complete in <30 ms on a synthesized tree, matching the
|
|
38
|
-
/// budget in `ROADMAP.md` (sub-E.10 perf bench).
|
|
39
|
-
///
|
|
40
|
-
/// The hierarchy under test is 50 nested UIView levels — much
|
|
41
|
-
/// deeper than any real app. The depth cap inside the helper
|
|
42
|
-
/// (`maxTreeDepth=10`) keeps the walk bounded so we shouldn't
|
|
43
|
-
/// blow past the budget even at extreme depth.
|
|
44
|
-
func testTreeWalkRespectsDepthCap() {
|
|
45
|
-
let root = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
|
|
46
|
-
var cursor = root
|
|
47
|
-
for _ in 0..<50 {
|
|
48
|
-
let child = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
|
|
49
|
-
cursor.addSubview(child)
|
|
50
|
-
cursor = child
|
|
51
|
-
}
|
|
52
|
-
measure {
|
|
53
|
-
// private method — guarded by `@testable` access.
|
|
54
|
-
// We're not asserting the output, just the timing budget.
|
|
55
|
-
_ = SentoriScreenshotCapture.value(forKey: "viewTreeForTesting")
|
|
56
|
-
.map { _ in 0 } ?? 0
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
// Phase 29 sub-A step 3: XCTest coverage for SentoriThreadSampler.
|
|
2
|
-
//
|
|
3
|
-
// Run via Xcode (target → SentoriTests, ⌘U) or via xcodebuild from the
|
|
4
|
-
// iOS host:
|
|
5
|
-
// xcodebuild test \
|
|
6
|
-
// -scheme SentoriTests \
|
|
7
|
-
// -destination 'platform=iOS Simulator,name=iPhone 15'
|
|
8
|
-
//
|
|
9
|
-
// On Apple Silicon Mac the simulator runs the arm64 slice and the
|
|
10
|
-
// sampler can walk frames; on Intel Mac the simulator slice is x86_64
|
|
11
|
-
// and the sampler returns []. Both paths are asserted below.
|
|
12
|
-
|
|
13
|
-
import XCTest
|
|
14
|
-
|
|
15
|
-
@testable import SentoriCrashHandler
|
|
16
|
-
|
|
17
|
-
final class SentoriThreadSamplerTests: XCTestCase {
|
|
18
|
-
|
|
19
|
-
override func setUp() {
|
|
20
|
-
super.setUp()
|
|
21
|
-
// Capture the main pthread → mach port mapping. setUp runs on
|
|
22
|
-
// the test runner's main queue, which is the main thread.
|
|
23
|
-
SentoriThreadSampler.installMainThreadHandle()
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/// Background → sampler → main: should collect a non-trivial frame
|
|
27
|
-
/// chain on arm64 simulators.
|
|
28
|
-
func testCaptureFromBackgroundReturnsAtLeastFiveFrames() {
|
|
29
|
-
let exp = expectation(description: "background sample")
|
|
30
|
-
DispatchQueue.global(qos: .userInitiated).async {
|
|
31
|
-
let frames = SentoriThreadSampler.captureMainThreadFrames(maxFrames: 64)
|
|
32
|
-
#if arch(arm64)
|
|
33
|
-
XCTAssertGreaterThanOrEqual(
|
|
34
|
-
frames.count, 5,
|
|
35
|
-
"expected ≥ 5 main-thread frames on arm64; got \(frames.count)"
|
|
36
|
-
)
|
|
37
|
-
if !frames.isEmpty {
|
|
38
|
-
XCTAssertGreaterThan(
|
|
39
|
-
frames[0].uint64Value, 0,
|
|
40
|
-
"first PC must be non-zero"
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
XCTAssertLessThanOrEqual(
|
|
44
|
-
frames.count, 64,
|
|
45
|
-
"must respect maxFrames cap"
|
|
46
|
-
)
|
|
47
|
-
#else
|
|
48
|
-
// Intel simulator: sampler returns empty by design.
|
|
49
|
-
XCTAssertEqual(frames.count, 0)
|
|
50
|
-
#endif
|
|
51
|
-
exp.fulfill()
|
|
52
|
-
}
|
|
53
|
-
wait(for: [exp], timeout: 5.0)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/// Sampling from main itself must refuse — would race with our own
|
|
57
|
-
/// register state.
|
|
58
|
-
func testCaptureFromMainReturnsEmpty() {
|
|
59
|
-
let frames = SentoriThreadSampler.captureMainThreadFrames(maxFrames: 64)
|
|
60
|
-
XCTAssertEqual(
|
|
61
|
-
frames.count, 0,
|
|
62
|
-
"sampling from main must return [] (would race with own state)"
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/// `installMainThreadHandle` must be safe to call repeatedly.
|
|
67
|
-
func testInstallIsIdempotent() {
|
|
68
|
-
SentoriThreadSampler.installMainThreadHandle()
|
|
69
|
-
SentoriThreadSampler.installMainThreadHandle()
|
|
70
|
-
SentoriThreadSampler.installMainThreadHandle()
|
|
71
|
-
|
|
72
|
-
let exp = expectation(description: "still works after re-install")
|
|
73
|
-
DispatchQueue.global().async {
|
|
74
|
-
let frames = SentoriThreadSampler.captureMainThreadFrames(maxFrames: 16)
|
|
75
|
-
#if arch(arm64)
|
|
76
|
-
XCTAssertGreaterThan(
|
|
77
|
-
frames.count, 0,
|
|
78
|
-
"re-installs must not break the captured handle"
|
|
79
|
-
)
|
|
80
|
-
#endif
|
|
81
|
-
exp.fulfill()
|
|
82
|
-
}
|
|
83
|
-
wait(for: [exp], timeout: 2.0)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/// `maxFrames: 0` returns empty even on arm64.
|
|
87
|
-
func testZeroMaxFramesReturnsEmpty() {
|
|
88
|
-
let exp = expectation(description: "zero max")
|
|
89
|
-
DispatchQueue.global().async {
|
|
90
|
-
let frames = SentoriThreadSampler.captureMainThreadFrames(maxFrames: 0)
|
|
91
|
-
XCTAssertEqual(frames.count, 0)
|
|
92
|
-
exp.fulfill()
|
|
93
|
-
}
|
|
94
|
-
wait(for: [exp], timeout: 2.0)
|
|
95
|
-
}
|
|
96
|
-
}
|