@luciq/react-native 19.4.0 → 19.6.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/.claude/agents/codebase-analyzer.md +33 -0
- package/.claude/agents/codebase-locator.md +42 -0
- package/.claude/agents/codebase-pattern-finder.md +40 -0
- package/.claude/commands/apply-pr-reviews.md +253 -0
- package/.claude/commands/create-jira-workitem.md +27 -0
- package/.claude/commands/create-pr.md +138 -0
- package/.claude/commands/create-public-release-notes.md +145 -0
- package/.claude/commands/create-rca.md +286 -0
- package/.claude/commands/debug-sdk.md +66 -0
- package/.claude/commands/describe-pr.md +40 -0
- package/.claude/commands/new-api.md +60 -0
- package/.claude/commands/new-feature.md +75 -0
- package/.claude/commands/pr-review.md +85 -0
- package/.claude/commands/research-codebase.md +41 -0
- package/.claude/commands/review.md +73 -0
- package/.claude/memory/MEMORY.md +1 -0
- package/.claude/memory/feedback_pr_title_format.md +10 -0
- package/.claude/rules/react-native-typescript.md +46 -0
- package/CHANGELOG.md +12 -0
- package/CLAUDE.md +125 -0
- package/android/native.gradle +1 -1
- package/android/src/main/java/ai/luciq/reactlibrary/LuciqScreenLoadingFrameTracker.java +88 -0
- package/android/src/main/java/ai/luciq/reactlibrary/RNLuciqAPMModule.java +184 -10
- package/android/src/main/java/ai/luciq/reactlibrary/RNLuciqReactnativeModule.java +5 -3
- package/dist/components/LuciqCaptureScreenLoading.d.ts +8 -0
- package/dist/components/LuciqCaptureScreenLoading.js +154 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/modules/APM.d.ts +19 -0
- package/dist/modules/APM.js +38 -0
- package/dist/modules/Luciq.d.ts +1 -1
- package/dist/modules/Luciq.js +169 -11
- package/dist/modules/apm/ScreenLoadingManager.d.ts +99 -0
- package/dist/modules/apm/ScreenLoadingManager.js +296 -0
- package/dist/native/NativeAPM.d.ts +9 -0
- package/dist/native/NativeLuciq.d.ts +1 -1
- package/dist/utils/LuciqUtils.d.ts +25 -0
- package/dist/utils/LuciqUtils.js +44 -0
- package/dist/utils/RouteMatcher.d.ts +30 -0
- package/dist/utils/RouteMatcher.js +67 -0
- package/ios/RNLuciq/LuciqAPMBridge.m +82 -0
- package/ios/RNLuciq/LuciqReactBridge.m +1 -1
- package/ios/RNLuciq/LuciqScreenLoadingFrameTracker.h +11 -0
- package/ios/RNLuciq/LuciqScreenLoadingFrameTracker.m +121 -0
- package/ios/RNLuciq/Util/LCQAPM+PrivateAPIs.h +14 -0
- package/ios/native.rb +1 -1
- package/package.json +4 -1
- package/scripts/get-github-app-token.sh +70 -0
- package/scripts/notify-github.sh +17 -8
- package/src/components/LuciqCaptureScreenLoading.tsx +210 -0
- package/src/index.ts +4 -0
- package/src/modules/APM.ts +42 -0
- package/src/modules/Luciq.ts +197 -11
- package/src/modules/apm/ScreenLoadingManager.ts +364 -0
- package/src/native/NativeAPM.ts +22 -0
- package/src/native/NativeLuciq.ts +1 -1
- package/src/utils/LuciqUtils.ts +49 -0
- package/src/utils/RouteMatcher.ts +83 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Code review checklist for React Native SDK best practices
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Code Review
|
|
6
|
+
|
|
7
|
+
Review the current changes against React Native SDK best practices.
|
|
8
|
+
|
|
9
|
+
## Review Categories
|
|
10
|
+
|
|
11
|
+
### 1. Native Bridge Safety
|
|
12
|
+
|
|
13
|
+
- [ ] NativeModule interfaces are consistent between TypeScript and native implementations
|
|
14
|
+
- [ ] NativeModule calls handle errors gracefully (no host app crashes)
|
|
15
|
+
- [ ] No breaking changes to native module APIs without version bump
|
|
16
|
+
- [ ] NativeEventEmitter subscriptions are properly cleaned up
|
|
17
|
+
|
|
18
|
+
### 2. Public API Compatibility
|
|
19
|
+
|
|
20
|
+
- [ ] No breaking changes to exports in `src/index.ts`
|
|
21
|
+
- [ ] New public APIs follow existing module pattern (exported functions in `src/modules/`)
|
|
22
|
+
- [ ] Deprecations use `@deprecated` JSDoc annotation with migration guidance
|
|
23
|
+
- [ ] API naming is consistent with existing conventions
|
|
24
|
+
|
|
25
|
+
### 3. Testing
|
|
26
|
+
|
|
27
|
+
- [ ] New code has corresponding tests in `test/`
|
|
28
|
+
- [ ] Native module mocks are updated in `test/mocks/` if needed
|
|
29
|
+
- [ ] Tests cover edge cases and error paths
|
|
30
|
+
- [ ] No flaky or timing-dependent tests
|
|
31
|
+
|
|
32
|
+
### 4. Error Handling
|
|
33
|
+
|
|
34
|
+
- [ ] Native module calls are wrapped with proper error handling
|
|
35
|
+
- [ ] No unhandled promises or missing `await`
|
|
36
|
+
- [ ] Errors don't crash the host app
|
|
37
|
+
- [ ] TypeScript strict mode is satisfied (no `any` escapes)
|
|
38
|
+
|
|
39
|
+
### 5. Performance
|
|
40
|
+
|
|
41
|
+
- [ ] No expensive operations on the JS thread
|
|
42
|
+
- [ ] Network interception doesn't block requests
|
|
43
|
+
- [ ] No unnecessary re-renders or event listener leaks
|
|
44
|
+
- [ ] Screen tracking doesn't interfere with navigation
|
|
45
|
+
|
|
46
|
+
### 6. Code Quality
|
|
47
|
+
|
|
48
|
+
- [ ] Passes `yarn lint` and `yarn format`
|
|
49
|
+
- [ ] TypeScript strict mode: no unused locals/params, no implicit any
|
|
50
|
+
- [ ] No unnecessary imports or dead code
|
|
51
|
+
- [ ] `import type` used for type-only imports
|
|
52
|
+
|
|
53
|
+
### 7. Cross-Platform
|
|
54
|
+
|
|
55
|
+
- [ ] Changes work on both iOS and Android
|
|
56
|
+
- [ ] Platform-specific code uses proper conditional logic
|
|
57
|
+
- [ ] Source map upload scripts still work for both platforms
|
|
58
|
+
|
|
59
|
+
### 8. Security
|
|
60
|
+
|
|
61
|
+
- [ ] No hardcoded secrets or API keys
|
|
62
|
+
- [ ] Sensitive data is handled appropriately
|
|
63
|
+
- [ ] User data handling respects privacy settings
|
|
64
|
+
- [ ] No logging of sensitive information in release mode
|
|
65
|
+
|
|
66
|
+
## How to Use
|
|
67
|
+
|
|
68
|
+
Run the current diff through each category. For each issue found, report:
|
|
69
|
+
|
|
70
|
+
- **File:line** - exact location
|
|
71
|
+
- **Severity** - critical / warning / suggestion
|
|
72
|
+
- **Issue** - what's wrong
|
|
73
|
+
- **Fix** - how to fix it
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
- [PR title format](feedback_pr_title_format.md) — PR titles must be prefixed with fix/chore/feat, same as commit convention
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: PR title format
|
|
3
|
+
description: PR titles must be prefixed with fix/chore/feat - same convention as commit messages
|
|
4
|
+
type: feedback
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
PR titles must always include a type prefix (fix, chore, feat, etc.) - same convention as commit messages.
|
|
8
|
+
|
|
9
|
+
**Why:** Team convention for consistent PR naming.
|
|
10
|
+
**How to apply:** When generating PR titles, always prefix with the type. Update the /create-pr command to reflect this format.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# AI rules for React Native TypeScript
|
|
2
|
+
|
|
3
|
+
You are an expert in React Native and TypeScript development. This is an SDK project (not an app), so focus on library design, native bridge patterns, and public API surface.
|
|
4
|
+
|
|
5
|
+
## Project Context
|
|
6
|
+
|
|
7
|
+
This is the Luciq React Native SDK (`@luciq/react-native`). It bridges JavaScript to native iOS/Android modules for observability features (bug reporting, crash reporting, APM, network logging, session replay, surveys).
|
|
8
|
+
|
|
9
|
+
## TypeScript Style
|
|
10
|
+
|
|
11
|
+
- **Strict mode** is enabled: `noUnusedLocals`, `noUnusedParameters`, `noImplicitAny`, `strict`
|
|
12
|
+
- Use `import type` for type-only imports (`importsNotUsedAsValues: "error"`)
|
|
13
|
+
- Prettier: single quotes, semicolons, 100 char width, trailing commas
|
|
14
|
+
- Import ordering via `@trivago/prettier-plugin-sort-imports`
|
|
15
|
+
- ESLint: `@react-native-community` config with prettier and jsdoc plugins
|
|
16
|
+
|
|
17
|
+
## Module Pattern
|
|
18
|
+
|
|
19
|
+
Each SDK feature is a module in `src/modules/` exported as a namespace:
|
|
20
|
+
|
|
21
|
+
- Module exports functions that call through to native via `NativeModules`
|
|
22
|
+
- Native bridge interfaces are typed in `src/native/`
|
|
23
|
+
- Events from native use `NativeEventEmitter`
|
|
24
|
+
- Models/types live in `src/models/`
|
|
25
|
+
- Enums and utilities in `src/utils/`
|
|
26
|
+
|
|
27
|
+
## Native Bridge
|
|
28
|
+
|
|
29
|
+
- JS -> Native: via React Native `NativeModules` (typed interfaces in `src/native/`)
|
|
30
|
+
- Native -> JS: via `NativeEventEmitter` for async callbacks
|
|
31
|
+
- iOS: Objective-C/Swift, managed via CocoaPods (`RNLuciq.podspec`)
|
|
32
|
+
- Android: Java/Kotlin, managed via Gradle (`android/build.gradle`)
|
|
33
|
+
|
|
34
|
+
## Testing
|
|
35
|
+
|
|
36
|
+
- Jest with `react-native` preset and `ts-jest`
|
|
37
|
+
- Test setup in `test/setup.ts` mocks all native modules and disables HTTP via nock
|
|
38
|
+
- Tests mirror src structure in `test/`
|
|
39
|
+
- Mock native modules in `test/mocks/`
|
|
40
|
+
|
|
41
|
+
## SDK-Specific Rules
|
|
42
|
+
|
|
43
|
+
- Never crash the host app - handle errors gracefully
|
|
44
|
+
- Public API changes must be backward compatible unless doing a major version bump
|
|
45
|
+
- Network interception has two modes: JavaScript (XHR wrapping) and Native
|
|
46
|
+
- The SDK supports React Navigation (v4, v6), react-native-navigation (v7), and Expo
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [19.6.0](https://github.com/luciqai/luciq-reactnative-sdk/compare/v19.6.0...19.4.0)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Add support for Screen Loading with react-navigation integration and manual reporting API.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Bump Luciq iOS SDK to v19.6.1 ([#49](https://github.com/luciqai/luciq-reactnative-sdk/pull/49)). [See release notes](https://github.com/luciqai/luciq-ios-sdk/releases/tag/19.6.1).
|
|
12
|
+
|
|
13
|
+
- Bump Luciq Android SDK to v19.6.0 ([#49](https://github.com/luciqai/luciq-reactnative-sdk/pull/49)). [See release notes](https://github.com/luciqai/luciq-android-sdk/releases/tag/v19.6.0).
|
|
14
|
+
|
|
3
15
|
## [19.4.0](https://github.com/luciqai/luciq-reactnative-sdk/compare/v19.4.0...19.3.0)
|
|
4
16
|
|
|
5
17
|
### Added
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
This is **@luciq/react-native** — a React Native SDK for Luciq's Agentic Observability Platform. It provides bug reporting, crash reporting, APM, network logging, session replay, surveys, and feature requests for mobile apps. The SDK bridges JavaScript to native iOS/Android modules.
|
|
8
|
+
|
|
9
|
+
## Common Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
yarn test # Run all tests (Jest)
|
|
13
|
+
yarn test -- --testPathPattern=test/modules/APM # Run a single test file
|
|
14
|
+
yarn lint # ESLint check
|
|
15
|
+
yarn lint:fix # ESLint autofix
|
|
16
|
+
yarn format # Prettier check
|
|
17
|
+
yarn format:fix # Prettier autofix
|
|
18
|
+
yarn build # Build library (tsc) + CLI (rollup)
|
|
19
|
+
yarn build:lib # TypeScript compilation only
|
|
20
|
+
yarn build:cli # Rollup CLI bundle only
|
|
21
|
+
yarn bootstrap # Full setup: install deps + pods for example app
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Architecture
|
|
25
|
+
|
|
26
|
+
### Module System
|
|
27
|
+
|
|
28
|
+
The SDK is organized as independent feature modules, each with a JS API layer and a native bridge:
|
|
29
|
+
|
|
30
|
+
- **`src/modules/Luciq.ts`** — Core module: `init()`, screen tracking, shake detection, feature flags, app lifecycle
|
|
31
|
+
- **`src/modules/APM.ts`** — Performance spans (startCustomSpan, addCompletedCustomSpan)
|
|
32
|
+
- **`src/modules/BugReporting.ts`** — User-initiated bug reports
|
|
33
|
+
- **`src/modules/CrashReporting.ts`** — Unhandled exception tracking
|
|
34
|
+
- **`src/modules/NetworkLogger.ts`** — HTTP interception with filtering/obfuscation (JS mode wraps fetch/XHR; native mode delegates to platform)
|
|
35
|
+
- **`src/modules/SessionReplay.ts`** — Screen recording
|
|
36
|
+
- **`src/modules/Surveys.ts`**, **`Replies.ts`**, **`FeatureRequests.ts`** — User engagement
|
|
37
|
+
|
|
38
|
+
### Native Bridge
|
|
39
|
+
|
|
40
|
+
`src/native/` contains typed interfaces for each native module (NativeLuciq, NativeAPM, etc.). JS calls native via React Native's `NativeModules`. Native-to-JS events use `NativeEventEmitter`.
|
|
41
|
+
|
|
42
|
+
### Other Key Directories
|
|
43
|
+
|
|
44
|
+
- **`src/models/`** — TypeScript data models (LuciqConfig, Report, CustomSpan, etc.)
|
|
45
|
+
- **`src/utils/`** — Enums, XhrNetworkInterceptor, CustomSpansManager
|
|
46
|
+
- **`cli/`** — CLI tool for source map/SO file uploads (Commander-based, bundled with Rollup)
|
|
47
|
+
- **`plugin/`** — Expo config plugin (withLuciq)
|
|
48
|
+
- **`ios/`** — Native iOS module (Objective-C/Swift, CocoaPods via `RNLuciq.podspec`, iOS 15.0+)
|
|
49
|
+
- **`android/`** — Native Android module (Gradle)
|
|
50
|
+
- **`test/`** — Jest tests mirroring src structure; `test/mocks/` has native module mocks; `test/setup.ts` configures mocks and disables network via nock
|
|
51
|
+
|
|
52
|
+
## Coding Standards
|
|
53
|
+
|
|
54
|
+
Code style, TypeScript/Prettier/ESLint config, module pattern, native-bridge conventions, testing conventions, and SDK-specific rules live in `.claude/rules/react-native-typescript.md`. That file is the single source of truth for how to write code in this repo - do not duplicate it here.
|
|
55
|
+
|
|
56
|
+
## Output
|
|
57
|
+
|
|
58
|
+
- Answer is always line 1. Reasoning comes after, never before.
|
|
59
|
+
- No preamble. No "Great question!", "Sure!", "Of course!", "Certainly!", "Absolutely!".
|
|
60
|
+
- No hollow closings. No "I hope this helps!", "Let me know if you need anything!".
|
|
61
|
+
- No restating the prompt. If the task is clear, execute immediately.
|
|
62
|
+
- No explaining what you are about to do. Just do it.
|
|
63
|
+
- No unsolicited suggestions. Do exactly what was asked, nothing more.
|
|
64
|
+
- Structured output only: bullets, tables, code blocks. Prose only when explicitly requested.
|
|
65
|
+
|
|
66
|
+
## Token Efficiency
|
|
67
|
+
|
|
68
|
+
- Compress responses. Every sentence must earn its place.
|
|
69
|
+
- No redundant context. Do not repeat information already established in the session.
|
|
70
|
+
- No long intros or transitions between sections.
|
|
71
|
+
- Short responses are correct unless depth is explicitly requested.
|
|
72
|
+
|
|
73
|
+
## Typography - ASCII Only
|
|
74
|
+
|
|
75
|
+
- No em dashes (-) - use hyphens (-)
|
|
76
|
+
- No smart/curly quotes - use straight quotes (" ')
|
|
77
|
+
- No ellipsis character - use three dots (...)
|
|
78
|
+
- No Unicode bullets - use hyphens (-) or asterisks (\*)
|
|
79
|
+
- No non-breaking spaces
|
|
80
|
+
|
|
81
|
+
## Sycophancy - Zero Tolerance
|
|
82
|
+
|
|
83
|
+
- Never validate the user before answering.
|
|
84
|
+
- Never say "You're absolutely right!" unless the user made a verifiable correct statement.
|
|
85
|
+
- Disagree when wrong. State the correction directly.
|
|
86
|
+
- Do not change a correct answer because the user pushes back.
|
|
87
|
+
|
|
88
|
+
## Accuracy and Speculation Control
|
|
89
|
+
|
|
90
|
+
- Never speculate about code, files, or APIs you have not read.
|
|
91
|
+
- If referencing a file or function: read it first, then answer.
|
|
92
|
+
- If unsure: say "I don't know." Never guess confidently.
|
|
93
|
+
- Never invent file paths, function names, or API signatures.
|
|
94
|
+
- If a user corrects a factual claim: accept it as ground truth for the entire session. Never re-assert the original claim.
|
|
95
|
+
|
|
96
|
+
## Code Output
|
|
97
|
+
|
|
98
|
+
- Return the simplest working solution. No over-engineering.
|
|
99
|
+
- No abstractions or helpers for single-use operations.
|
|
100
|
+
- No speculative features or future-proofing.
|
|
101
|
+
- No docstrings or comments on code that was not changed.
|
|
102
|
+
- Inline comments only where logic is non-obvious.
|
|
103
|
+
- Read the file before modifying it. Never edit blind.
|
|
104
|
+
|
|
105
|
+
## Warnings and Disclaimers
|
|
106
|
+
|
|
107
|
+
- No safety disclaimers unless there is a genuine life-safety or legal risk.
|
|
108
|
+
- No "Note that...", "Keep in mind that...", "It's worth mentioning..." soft warnings.
|
|
109
|
+
- No "As an AI, I..." framing.
|
|
110
|
+
|
|
111
|
+
## Session Memory
|
|
112
|
+
|
|
113
|
+
- Learn user corrections and preferences within the session.
|
|
114
|
+
- Apply them silently. Do not re-announce learned behavior.
|
|
115
|
+
- If the user corrects a mistake: fix it, remember it, move on.
|
|
116
|
+
|
|
117
|
+
## Scope Control
|
|
118
|
+
|
|
119
|
+
- Do not add features beyond what was asked.
|
|
120
|
+
- Do not refactor surrounding code when fixing a bug.
|
|
121
|
+
- Do not create new files unless strictly necessary.
|
|
122
|
+
|
|
123
|
+
## Override Rule
|
|
124
|
+
|
|
125
|
+
User instructions always override this file.
|
package/android/native.gradle
CHANGED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
package ai.luciq.reactlibrary;
|
|
2
|
+
|
|
3
|
+
import android.os.Handler;
|
|
4
|
+
import android.os.Looper;
|
|
5
|
+
import android.view.Choreographer;
|
|
6
|
+
import java.util.HashMap;
|
|
7
|
+
import java.util.HashSet;
|
|
8
|
+
import java.util.Map;
|
|
9
|
+
import java.util.Set;
|
|
10
|
+
import android.util.Log;
|
|
11
|
+
|
|
12
|
+
public class LuciqScreenLoadingFrameTracker {
|
|
13
|
+
private static final String TAG = "ScreenLoading";
|
|
14
|
+
private static LuciqScreenLoadingFrameTracker instance;
|
|
15
|
+
|
|
16
|
+
private final Map<String, Long> spanIdToTimestamp = new HashMap<>();
|
|
17
|
+
private final Set<String> activeSpanIds = new HashSet<>();
|
|
18
|
+
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
|
19
|
+
private static final int MAX_STORAGE_CAPACITY = 50;
|
|
20
|
+
|
|
21
|
+
private LuciqScreenLoadingFrameTracker() {}
|
|
22
|
+
|
|
23
|
+
public static synchronized LuciqScreenLoadingFrameTracker getInstance() {
|
|
24
|
+
if (instance == null) {
|
|
25
|
+
instance = new LuciqScreenLoadingFrameTracker();
|
|
26
|
+
}
|
|
27
|
+
return instance;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public void initializeFrameTracking() {
|
|
31
|
+
// Choreographer is automatically available on Android
|
|
32
|
+
Log.d(TAG, "Frame tracking initialized");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public void startTrackingForSpanId(final String spanId) {
|
|
36
|
+
mainHandler.post(new Runnable() {
|
|
37
|
+
@Override
|
|
38
|
+
public void run() {
|
|
39
|
+
activeSpanIds.add(spanId);
|
|
40
|
+
Log.d(TAG, "Started tracking for span " + spanId);
|
|
41
|
+
|
|
42
|
+
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
|
|
43
|
+
@Override
|
|
44
|
+
public void doFrame(long frameTimeNanos) {
|
|
45
|
+
if (activeSpanIds.contains(spanId)) {
|
|
46
|
+
// frameTimeNanos is monotonic clock (nanoseconds since device boot)
|
|
47
|
+
// Convert to epoch-based microseconds using offset correction
|
|
48
|
+
long currentMonotonicNanos = System.nanoTime();
|
|
49
|
+
long currentEpochMillis = System.currentTimeMillis();
|
|
50
|
+
long nanosSinceFrame = currentMonotonicNanos - frameTimeNanos;
|
|
51
|
+
long frameEpochMicroseconds = (currentEpochMillis * 1000) - (nanosSinceFrame / 1000);
|
|
52
|
+
|
|
53
|
+
spanIdToTimestamp.put(spanId, frameEpochMicroseconds);
|
|
54
|
+
activeSpanIds.remove(spanId);
|
|
55
|
+
Log.d(TAG, "Frame rendered for span " + spanId + " at " + frameEpochMicroseconds +
|
|
56
|
+
"μs (frame offset: " + String.format("%.3f", nanosSinceFrame / 1_000_000.0) + "ms)");
|
|
57
|
+
|
|
58
|
+
if (spanIdToTimestamp.size() > MAX_STORAGE_CAPACITY) {
|
|
59
|
+
cleanup();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public Long getFrameTimestampForSpanId(String spanId) {
|
|
69
|
+
Long timestamp = spanIdToTimestamp.get(spanId);
|
|
70
|
+
if (timestamp != null) {
|
|
71
|
+
spanIdToTimestamp.remove(spanId);
|
|
72
|
+
Log.d(TAG, "Retrieved timestamp " + timestamp + "μs for span " + spanId);
|
|
73
|
+
}
|
|
74
|
+
return timestamp;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private void cleanup() {
|
|
78
|
+
// Keep only the most recent 30 entries
|
|
79
|
+
if (spanIdToTimestamp.size() > 30) {
|
|
80
|
+
// Simple cleanup: remove oldest entries
|
|
81
|
+
int toRemove = spanIdToTimestamp.size() - 30;
|
|
82
|
+
for (String key : new HashSet<>(spanIdToTimestamp.keySet())) {
|
|
83
|
+
if (toRemove-- <= 0) break;
|
|
84
|
+
spanIdToTimestamp.remove(key);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
package ai.luciq.reactlibrary;
|
|
3
2
|
|
|
4
3
|
import static ai.luciq.reactlibrary.utils.LuciqUtil.getMethod;
|
|
@@ -15,6 +14,8 @@ import com.facebook.react.bridge.ReactMethod;
|
|
|
15
14
|
import com.facebook.react.bridge.ReadableMap;
|
|
16
15
|
|
|
17
16
|
import java.lang.reflect.Method;
|
|
17
|
+
import java.util.HashMap;
|
|
18
|
+
import java.util.Map;
|
|
18
19
|
import java.util.Date;
|
|
19
20
|
|
|
20
21
|
import javax.annotation.Nonnull;
|
|
@@ -34,7 +35,6 @@ public class RNLuciqAPMModule extends EventEmitterModule {
|
|
|
34
35
|
super(reactApplicationContext);
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
|
|
38
38
|
@Nonnull
|
|
39
39
|
@Override
|
|
40
40
|
public String getName() {
|
|
@@ -351,14 +351,7 @@ public class RNLuciqAPMModule extends EventEmitterModule {
|
|
|
351
351
|
} catch (Exception e) {
|
|
352
352
|
e.printStackTrace();
|
|
353
353
|
}
|
|
354
|
-
APMCPNetworkLog.W3CExternalTraceAttributes w3cExternalTraceAttributes =
|
|
355
|
-
new APMCPNetworkLog.W3CExternalTraceAttributes(
|
|
356
|
-
isW3cHeaderFound,
|
|
357
|
-
partialId,
|
|
358
|
-
networkStartTimeInSeconds,
|
|
359
|
-
w3cAttributes.getString("w3cGeneratedHeader"),
|
|
360
|
-
w3cAttributes.getString("w3cCaughtHeader")
|
|
361
|
-
);
|
|
354
|
+
APMCPNetworkLog.W3CExternalTraceAttributes w3cExternalTraceAttributes = new APMCPNetworkLog.W3CExternalTraceAttributes(isW3cHeaderFound, partialId, networkStartTimeInSeconds, w3cAttributes.getString("w3cGeneratedHeader"), w3cAttributes.getString("w3cCaughtHeader"));
|
|
362
355
|
try {
|
|
363
356
|
Method method = getMethod(Class.forName("ai.luciq.apm.networking.APMNetworkLogger"), "log", long.class, long.class, String.class, String.class, long.class, String.class, String.class, String.class, String.class, String.class, long.class, int.class, String.class, String.class, String.class, String.class, APMCPNetworkLog.W3CExternalTraceAttributes.class);
|
|
364
357
|
if (method != null) {
|
|
@@ -493,4 +486,185 @@ public class RNLuciqAPMModule extends EventEmitterModule {
|
|
|
493
486
|
}
|
|
494
487
|
});
|
|
495
488
|
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Initialize screen frame tracking for Screen Loading feature
|
|
492
|
+
*/
|
|
493
|
+
@ReactMethod
|
|
494
|
+
public void initScreenFrameTracking(Promise promise) {
|
|
495
|
+
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
496
|
+
@Override
|
|
497
|
+
public void run() {
|
|
498
|
+
LuciqScreenLoadingFrameTracker.getInstance().initializeFrameTracking();
|
|
499
|
+
promise.resolve(null);
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Set the active screen span ID for frame tracking
|
|
506
|
+
*
|
|
507
|
+
* @param spanId the span ID to track
|
|
508
|
+
*/
|
|
509
|
+
@ReactMethod
|
|
510
|
+
public void setActiveScreenSpanId(String spanId) {
|
|
511
|
+
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
512
|
+
@Override
|
|
513
|
+
public void run() {
|
|
514
|
+
LuciqScreenLoadingFrameTracker.getInstance().startTrackingForSpanId(spanId);
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Get the frame timestamp for a given span ID
|
|
521
|
+
*
|
|
522
|
+
* @param spanId the span ID to retrieve the timestamp for
|
|
523
|
+
* @param promise promise to resolve with the timestamp
|
|
524
|
+
*/
|
|
525
|
+
@ReactMethod
|
|
526
|
+
public void getScreenTimeToDisplay(String spanId, Promise promise) {
|
|
527
|
+
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
528
|
+
@Override
|
|
529
|
+
public void run() {
|
|
530
|
+
Long timestamp = LuciqScreenLoadingFrameTracker.getInstance().getFrameTimestampForSpanId(spanId);
|
|
531
|
+
promise.resolve(timestamp != null ? timestamp.doubleValue() : null);
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Check if Screen Loading feature is enabled
|
|
538
|
+
*
|
|
539
|
+
* @param promise promise to resolve with enabled status
|
|
540
|
+
*/
|
|
541
|
+
@ReactMethod
|
|
542
|
+
public void isScreenLoadingEnabled(Promise promise) {
|
|
543
|
+
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
544
|
+
@Override
|
|
545
|
+
public void run() {
|
|
546
|
+
try {
|
|
547
|
+
InternalAPM._isFeatureEnabledCP(APMFeature.SCREEN_LOADING, "LuciqCaptureScreenLoading", new FeatureAvailabilityCallback() {
|
|
548
|
+
@Override
|
|
549
|
+
public void invoke(boolean isFeatureAvailable) {
|
|
550
|
+
promise.resolve(isFeatureAvailable);
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
} catch (Exception e) {
|
|
554
|
+
promise.resolve(false);
|
|
555
|
+
e.printStackTrace();
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Enables or disables screen loading
|
|
563
|
+
*
|
|
564
|
+
* @param isEnabled boolean indicating enabled or disabled.
|
|
565
|
+
*/
|
|
566
|
+
@ReactMethod
|
|
567
|
+
public void setScreenLoadingEnabled(boolean isEnabled) {
|
|
568
|
+
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
569
|
+
@Override
|
|
570
|
+
public void run() {
|
|
571
|
+
try {
|
|
572
|
+
APM.setScreenLoadingEnabled(isEnabled);
|
|
573
|
+
} catch (Exception e) {
|
|
574
|
+
e.printStackTrace();
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Syncs screen loading data to native layer for reporting
|
|
582
|
+
*
|
|
583
|
+
* @param spanId the span ID
|
|
584
|
+
* @param screenName the name of the screen
|
|
585
|
+
* @param startTimestamp the start timestamp in microseconds
|
|
586
|
+
* @param duration_us the time to initial display in microseconds
|
|
587
|
+
* @param stages custom attributes attached to the span
|
|
588
|
+
*/
|
|
589
|
+
private static final String[] STAGE_KEYS = {"cnst_mus_st", "cnst_mus", "rnd_mus_st", "rnd_mus", "mnt_mus_st", "mnt_mus", "lyt_mus_st", "lyt_mus"};
|
|
590
|
+
|
|
591
|
+
private Map<String, Long> buildStagesMap(ReadableMap stages) {
|
|
592
|
+
final Map<String, Long> stagesMap = new HashMap<>();
|
|
593
|
+
for (String key : STAGE_KEYS) {
|
|
594
|
+
if (stages.hasKey(key)) stagesMap.put(key, (long) stages.getDouble(key));
|
|
595
|
+
}
|
|
596
|
+
return stagesMap;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
@ReactMethod
|
|
600
|
+
public void syncScreenLoading(double spanId, String screenName, double startTimestamp, double duration_us, ReadableMap stages) {
|
|
601
|
+
try {
|
|
602
|
+
final Map<String, Long> stagesMap = buildStagesMap(stages);
|
|
603
|
+
InternalAPM._reportScreenLoadingCP((long) startTimestamp, (long) duration_us, (long) spanId, stagesMap);
|
|
604
|
+
} catch (Exception e) {
|
|
605
|
+
e.printStackTrace();
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Syncs manual screen loading measurements to native layer for reporting.
|
|
611
|
+
* Unlike syncScreenLoading, this does not require a span ID.
|
|
612
|
+
*
|
|
613
|
+
* @param screenName the name of the screen
|
|
614
|
+
* @param startTimestamp the start timestamp in microseconds
|
|
615
|
+
* @param duration_us the duration in microseconds
|
|
616
|
+
* @param stages custom attributes attached to the measurement
|
|
617
|
+
*/
|
|
618
|
+
@ReactMethod
|
|
619
|
+
public void syncManualScreenLoading(String screenName, double startTimestamp, double duration_us, ReadableMap stages) {
|
|
620
|
+
try {
|
|
621
|
+
final Map<String, Long> stagesMap = buildStagesMap(stages);
|
|
622
|
+
InternalAPM._reportManualScreenLoadingCP(screenName, (long) startTimestamp, (long) duration_us, stagesMap);
|
|
623
|
+
} catch (Exception e) {
|
|
624
|
+
e.printStackTrace();
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Check if Screen Loading feature is enabled
|
|
630
|
+
*
|
|
631
|
+
* @param promise promise to resolve with enabled status
|
|
632
|
+
*/
|
|
633
|
+
@ReactMethod
|
|
634
|
+
public void isEndScreenLoadingEnabled(Promise promise) {
|
|
635
|
+
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
636
|
+
@Override
|
|
637
|
+
public void run() {
|
|
638
|
+
try {
|
|
639
|
+
InternalAPM._isFeatureEnabledCP(APMFeature.END_SCREEN_LOADING, "LuciqCaptureScreenLoading", new FeatureAvailabilityCallback() {
|
|
640
|
+
@Override
|
|
641
|
+
public void invoke(boolean isFeatureAvailable) {
|
|
642
|
+
promise.resolve(isFeatureAvailable);
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
} catch (Exception e) {
|
|
646
|
+
promise.resolve(false);
|
|
647
|
+
e.printStackTrace();
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* This method is responsible for extend the end time if the screen loading custom
|
|
655
|
+
* trace. It takes two parameters:
|
|
656
|
+
*
|
|
657
|
+
* @param timeStampMicro: A number representing the timestamp in microseconds when the screen loading
|
|
658
|
+
* custom trace is ending.
|
|
659
|
+
* @param uiTraceId: A number representing the unique identifier for the UI trace associated with the
|
|
660
|
+
* screen loading.
|
|
661
|
+
*/
|
|
662
|
+
@ReactMethod
|
|
663
|
+
public void endScreenLoading(double timeStampMicro, double uiTraceId) {
|
|
664
|
+
try {
|
|
665
|
+
InternalAPM._endScreenLoadingCP((long) timeStampMicro, (long) uiTraceId);
|
|
666
|
+
} catch (Exception e) {
|
|
667
|
+
e.printStackTrace();
|
|
668
|
+
}
|
|
669
|
+
}
|
|
496
670
|
}
|
|
@@ -1078,16 +1078,18 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1078
1078
|
* Reports that the screen has been changed (Repro Steps) the screen sent to this method will be the 'current view' on the dashboard
|
|
1079
1079
|
*
|
|
1080
1080
|
* @param screenName string containing the screen name
|
|
1081
|
+
* @param spanId the span ID for screen loading tracking (nullable)
|
|
1081
1082
|
*/
|
|
1082
1083
|
@ReactMethod
|
|
1083
|
-
public void reportScreenChange(final String screenName) {
|
|
1084
|
+
public void reportScreenChange(final String screenName, @Nullable final String spanId) {
|
|
1084
1085
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
1085
1086
|
@Override
|
|
1086
1087
|
public void run() {
|
|
1087
1088
|
try {
|
|
1088
|
-
|
|
1089
|
+
Long uiTraceId = spanId != null ? Long.parseLong(spanId) : null;
|
|
1090
|
+
Method method = getMethod(Class.forName("ai.luciq.library.Luciq"), "reportScreenChange", Bitmap.class, String.class , Long.class);
|
|
1089
1091
|
if (method != null) {
|
|
1090
|
-
method.invoke(null, null, screenName);
|
|
1092
|
+
method.invoke(null, null, screenName , uiTraceId);
|
|
1091
1093
|
}
|
|
1092
1094
|
} catch (Exception e) {
|
|
1093
1095
|
e.printStackTrace();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ViewProps } from 'react-native';
|
|
3
|
+
export interface LuciqScreenLoadingProps extends ViewProps {
|
|
4
|
+
screenName: string;
|
|
5
|
+
record?: boolean;
|
|
6
|
+
onMeasured?: (ttid: number) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function LuciqCaptureScreenLoading(props: LuciqScreenLoadingProps): React.JSX.Element;
|