@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.
Files changed (58) hide show
  1. package/.claude/agents/codebase-analyzer.md +33 -0
  2. package/.claude/agents/codebase-locator.md +42 -0
  3. package/.claude/agents/codebase-pattern-finder.md +40 -0
  4. package/.claude/commands/apply-pr-reviews.md +253 -0
  5. package/.claude/commands/create-jira-workitem.md +27 -0
  6. package/.claude/commands/create-pr.md +138 -0
  7. package/.claude/commands/create-public-release-notes.md +145 -0
  8. package/.claude/commands/create-rca.md +286 -0
  9. package/.claude/commands/debug-sdk.md +66 -0
  10. package/.claude/commands/describe-pr.md +40 -0
  11. package/.claude/commands/new-api.md +60 -0
  12. package/.claude/commands/new-feature.md +75 -0
  13. package/.claude/commands/pr-review.md +85 -0
  14. package/.claude/commands/research-codebase.md +41 -0
  15. package/.claude/commands/review.md +73 -0
  16. package/.claude/memory/MEMORY.md +1 -0
  17. package/.claude/memory/feedback_pr_title_format.md +10 -0
  18. package/.claude/rules/react-native-typescript.md +46 -0
  19. package/CHANGELOG.md +12 -0
  20. package/CLAUDE.md +125 -0
  21. package/android/native.gradle +1 -1
  22. package/android/src/main/java/ai/luciq/reactlibrary/LuciqScreenLoadingFrameTracker.java +88 -0
  23. package/android/src/main/java/ai/luciq/reactlibrary/RNLuciqAPMModule.java +184 -10
  24. package/android/src/main/java/ai/luciq/reactlibrary/RNLuciqReactnativeModule.java +5 -3
  25. package/dist/components/LuciqCaptureScreenLoading.d.ts +8 -0
  26. package/dist/components/LuciqCaptureScreenLoading.js +154 -0
  27. package/dist/index.d.ts +2 -0
  28. package/dist/index.js +2 -0
  29. package/dist/modules/APM.d.ts +19 -0
  30. package/dist/modules/APM.js +38 -0
  31. package/dist/modules/Luciq.d.ts +1 -1
  32. package/dist/modules/Luciq.js +169 -11
  33. package/dist/modules/apm/ScreenLoadingManager.d.ts +99 -0
  34. package/dist/modules/apm/ScreenLoadingManager.js +296 -0
  35. package/dist/native/NativeAPM.d.ts +9 -0
  36. package/dist/native/NativeLuciq.d.ts +1 -1
  37. package/dist/utils/LuciqUtils.d.ts +25 -0
  38. package/dist/utils/LuciqUtils.js +44 -0
  39. package/dist/utils/RouteMatcher.d.ts +30 -0
  40. package/dist/utils/RouteMatcher.js +67 -0
  41. package/ios/RNLuciq/LuciqAPMBridge.m +82 -0
  42. package/ios/RNLuciq/LuciqReactBridge.m +1 -1
  43. package/ios/RNLuciq/LuciqScreenLoadingFrameTracker.h +11 -0
  44. package/ios/RNLuciq/LuciqScreenLoadingFrameTracker.m +121 -0
  45. package/ios/RNLuciq/Util/LCQAPM+PrivateAPIs.h +14 -0
  46. package/ios/native.rb +1 -1
  47. package/package.json +4 -1
  48. package/scripts/get-github-app-token.sh +70 -0
  49. package/scripts/notify-github.sh +17 -8
  50. package/src/components/LuciqCaptureScreenLoading.tsx +210 -0
  51. package/src/index.ts +4 -0
  52. package/src/modules/APM.ts +42 -0
  53. package/src/modules/Luciq.ts +197 -11
  54. package/src/modules/apm/ScreenLoadingManager.ts +364 -0
  55. package/src/native/NativeAPM.ts +22 -0
  56. package/src/native/NativeLuciq.ts +1 -1
  57. package/src/utils/LuciqUtils.ts +49 -0
  58. 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.
@@ -1,5 +1,5 @@
1
1
  project.ext.luciq = [
2
- version: '19.4.0'
2
+ version: '19.6.0'
3
3
  ]
4
4
 
5
5
  dependencies {
@@ -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
- Method method = getMethod(Class.forName("ai.luciq.library.Luciq"), "reportScreenChange", Bitmap.class, String.class);
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;