@luciq/react-native 19.4.0-47504-SNAPSHOT → 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/proguard-rules.txt +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 -19
- package/android/src/main/java/ai/luciq/reactlibrary/RNLuciqNetworkLoggerModule.java +7 -29
- package/android/src/main/java/ai/luciq/reactlibrary/RNLuciqReactnativeModule.java +14 -34
- package/android/src/main/java/ai/luciq/reactlibrary/utils/EventEmitterModule.java +0 -7
- 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 +170 -13
- package/dist/modules/NetworkLogger.d.ts +5 -0
- package/dist/modules/NetworkLogger.js +1 -9
- 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/FeatureFlags.d.ts +0 -6
- package/dist/utils/FeatureFlags.js +0 -35
- package/dist/utils/LuciqUtils.d.ts +25 -0
- package/dist/utils/LuciqUtils.js +44 -6
- package/dist/utils/RouteMatcher.d.ts +30 -0
- package/dist/utils/RouteMatcher.js +67 -0
- package/dist/utils/XhrNetworkInterceptor.js +53 -85
- 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 +5 -1
- 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 +198 -14
- package/src/modules/NetworkLogger.ts +1 -26
- 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/FeatureFlags.ts +0 -44
- package/src/utils/LuciqUtils.ts +49 -15
- package/src/utils/RouteMatcher.ts +83 -0
- package/src/utils/XhrNetworkInterceptor.ts +55 -128
package/dist/modules/Luciq.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { AppState, findNodeHandle, Platform } from 'react-native';
|
|
2
2
|
import Report from '../models/Report';
|
|
3
3
|
import { emitter, NativeEvents, NativeLuciq } from '../native/NativeLuciq';
|
|
4
|
-
import { registerFeatureFlagsListener
|
|
4
|
+
import { registerFeatureFlagsListener } from '../utils/FeatureFlags';
|
|
5
5
|
import { LogLevel, NetworkInterceptionMode, ReproStepsMode, StringKey, } from '../utils/Enums';
|
|
6
6
|
import LuciqUtils, { checkNetworkRequestHandlers, resetNativeObfuscationListener, setApmNetworkFlagsIfChanged, stringifyIfNotString, } from '../utils/LuciqUtils';
|
|
7
7
|
import * as NetworkLogger from './NetworkLogger';
|
|
8
8
|
import { captureUnhandledRejections } from '../utils/UnhandledRejectionTracking';
|
|
9
|
+
import { ScreenLoadingManager } from './apm/ScreenLoadingManager';
|
|
9
10
|
import { addAppStateListener } from '../utils/AppStatesHandler';
|
|
10
11
|
import { NativeNetworkLogger } from '../native/NativeNetworkLogger';
|
|
11
12
|
import LuciqConstants from '../utils/LuciqConstants';
|
|
@@ -19,6 +20,12 @@ let _currentAppState = AppState.currentState;
|
|
|
19
20
|
let isNativeInterceptionFeatureEnabled = false; // Checks the value of "cp_native_interception_enabled" backend flag.
|
|
20
21
|
let hasAPMNetworkPlugin = false; // Android only: checks if the APM plugin is installed.
|
|
21
22
|
let shouldEnableNativeInterception = false; // For Android: used to disable APM logging inside reportNetworkLog() -> NativeAPM.networkLogAndroid(), For iOS: used to control native interception (true == enabled , false == disabled)
|
|
23
|
+
// Screen Loading tracking variables
|
|
24
|
+
let _navigationRef = null;
|
|
25
|
+
let _currentRoute = null;
|
|
26
|
+
let _activeNavigationSpanId = null;
|
|
27
|
+
let _stateChangeTimeout;
|
|
28
|
+
const STATE_CHANGE_TIMEOUT_MS = 2000; // Safety timeout if state never changes
|
|
22
29
|
/**
|
|
23
30
|
* Enables or disables Luciq functionality.
|
|
24
31
|
* @param isEnabled A boolean to enable/disable Luciq.
|
|
@@ -50,7 +57,6 @@ function reportCurrentViewForAndroid(screenName) {
|
|
|
50
57
|
* @param config SDK configurations. See {@link LuciqConfig} for more info.
|
|
51
58
|
*/
|
|
52
59
|
export const init = (config) => {
|
|
53
|
-
initFeatureFlagsCache();
|
|
54
60
|
if (Platform.OS === 'android') {
|
|
55
61
|
// Add android feature flags listener for android
|
|
56
62
|
registerFeatureFlagsListener();
|
|
@@ -79,7 +85,7 @@ export const init = (config) => {
|
|
|
79
85
|
reportCurrentViewForAndroid(firstScreen);
|
|
80
86
|
setTimeout(() => {
|
|
81
87
|
if (_currentScreen === firstScreen) {
|
|
82
|
-
NativeLuciq.reportScreenChange(firstScreen);
|
|
88
|
+
NativeLuciq.reportScreenChange(firstScreen, null);
|
|
83
89
|
_currentScreen = null;
|
|
84
90
|
}
|
|
85
91
|
}, 1000);
|
|
@@ -116,12 +122,14 @@ export const setWebViewUserInteractionsTrackingEnabled = (isEnabled) => {
|
|
|
116
122
|
* Handles app state changes and updates APM network flags if necessary.
|
|
117
123
|
*/
|
|
118
124
|
const handleAppStateChange = async (nextAppState, config) => {
|
|
119
|
-
// Checks if
|
|
125
|
+
// Checks if the app has come to the foreground
|
|
120
126
|
if (['inactive', 'background'].includes(_currentAppState) && nextAppState === 'active') {
|
|
121
127
|
const isUpdated = await fetchApmNetworkFlags();
|
|
122
128
|
if (isUpdated) {
|
|
123
129
|
refreshAPMNetworkConfigs(config);
|
|
124
130
|
}
|
|
131
|
+
// Refresh screen loading flags from native
|
|
132
|
+
await ScreenLoadingManager.refreshFlags();
|
|
125
133
|
}
|
|
126
134
|
_currentAppState = nextAppState;
|
|
127
135
|
};
|
|
@@ -647,21 +655,150 @@ export const onReportSubmitHandler = (handler) => {
|
|
|
647
655
|
});
|
|
648
656
|
NativeLuciq.setPreSendingHandler(handler);
|
|
649
657
|
};
|
|
658
|
+
/**
|
|
659
|
+
* Helper to clear the state change timeout
|
|
660
|
+
*/
|
|
661
|
+
const _clearStateChangeTimeout = () => {
|
|
662
|
+
if (_stateChangeTimeout) {
|
|
663
|
+
clearTimeout(_stateChangeTimeout);
|
|
664
|
+
_stateChangeTimeout = undefined;
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
/**
|
|
668
|
+
* Handles React Navigation's __unsafe_action__ event
|
|
669
|
+
* This fires WHEN a navigation action is dispatched (the start of navigation)
|
|
670
|
+
*/
|
|
671
|
+
const _onNavigationAction = (event) => {
|
|
672
|
+
// Check for noop actions that shouldn't create spans
|
|
673
|
+
if (event?.data?.noop) {
|
|
674
|
+
Logger.log('[ScreenLoading] Navigation action is a noop, not starting span');
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
// Skip non-navigation actions (like SET_PARAMS, OPEN_DRAWER, etc.)
|
|
678
|
+
const actionType = event?.data?.action?.type;
|
|
679
|
+
if (actionType &&
|
|
680
|
+
['SET_PARAMS', 'OPEN_DRAWER', 'CLOSE_DRAWER', 'TOGGLE_DRAWER'].includes(actionType)) {
|
|
681
|
+
Logger.log(`[ScreenLoading] Skipping non-navigation action: ${actionType}`);
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
// If there's an existing active span, it means navigation was interrupted
|
|
685
|
+
// Discard the previous span as it never completed
|
|
686
|
+
if (_activeNavigationSpanId) {
|
|
687
|
+
Logger.log('[ScreenLoading] Discarding incomplete previous navigation span');
|
|
688
|
+
// Mark the span as cancelled/error since state change never occurred
|
|
689
|
+
const span = ScreenLoadingManager.getActiveSpan(_activeNavigationSpanId);
|
|
690
|
+
if (span) {
|
|
691
|
+
ScreenLoadingManager.endSpan(_activeNavigationSpanId);
|
|
692
|
+
}
|
|
693
|
+
_activeNavigationSpanId = null;
|
|
694
|
+
_clearStateChangeTimeout();
|
|
695
|
+
}
|
|
696
|
+
// Create a new span for this navigation action
|
|
697
|
+
// We don't know the destination screen yet, so use a placeholder name
|
|
698
|
+
if (ScreenLoadingManager.isFeatureEnabled()) {
|
|
699
|
+
const span = ScreenLoadingManager.createSpan('NavigationPending', false);
|
|
700
|
+
if (span) {
|
|
701
|
+
_activeNavigationSpanId = span.spanId;
|
|
702
|
+
Logger.log(`[ScreenLoading] Started span ${span.spanId} on navigation dispatch`);
|
|
703
|
+
// Set a safety timeout to discard the span if state never changes
|
|
704
|
+
// This prevents memory leaks from incomplete navigations
|
|
705
|
+
_stateChangeTimeout = setTimeout(() => {
|
|
706
|
+
if (_activeNavigationSpanId === span.spanId) {
|
|
707
|
+
Logger.warn(`[ScreenLoading] Navigation span ${span.spanId} timed out - state never changed`);
|
|
708
|
+
ScreenLoadingManager.endSpan(span.spanId);
|
|
709
|
+
_activeNavigationSpanId = null;
|
|
710
|
+
}
|
|
711
|
+
}, STATE_CHANGE_TIMEOUT_MS);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
/**
|
|
716
|
+
* Handles React Navigation's state event
|
|
717
|
+
* This fires AFTER the navigation state has changed (the screen is mounted)
|
|
718
|
+
*/
|
|
719
|
+
const _onNavigationStateChange = () => {
|
|
720
|
+
if (!_navigationRef?.current) {
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const previousRouteName = _currentRoute;
|
|
724
|
+
const currentRoute = _navigationRef.current.getCurrentRoute();
|
|
725
|
+
const currentRouteName = currentRoute?.name || null;
|
|
726
|
+
// If no route or same route, ignore
|
|
727
|
+
if (!currentRouteName || previousRouteName === currentRouteName) {
|
|
728
|
+
// Still need to clean up the span if one was created
|
|
729
|
+
if (_activeNavigationSpanId) {
|
|
730
|
+
Logger.log('[ScreenLoading] Navigation resulted in same route, discarding span');
|
|
731
|
+
ScreenLoadingManager.endSpan(_activeNavigationSpanId);
|
|
732
|
+
_activeNavigationSpanId = null;
|
|
733
|
+
_clearStateChangeTimeout();
|
|
734
|
+
}
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
// Capture the span ID BEFORE clearing it so we can pass it to reportScreenChange
|
|
738
|
+
let spanIdForReport = _activeNavigationSpanId;
|
|
739
|
+
// Complete the active navigation span if one exists
|
|
740
|
+
if (_activeNavigationSpanId) {
|
|
741
|
+
// Now that we know the actual route name, check if it's excluded
|
|
742
|
+
if (ScreenLoadingManager.isRouteExcluded(currentRouteName)) {
|
|
743
|
+
Logger.log(`[ScreenLoading] Route "${currentRouteName}" is excluded, discarding span`);
|
|
744
|
+
ScreenLoadingManager.discardSpan(_activeNavigationSpanId);
|
|
745
|
+
spanIdForReport = null;
|
|
746
|
+
_activeNavigationSpanId = null;
|
|
747
|
+
_clearStateChangeTimeout();
|
|
748
|
+
}
|
|
749
|
+
else {
|
|
750
|
+
const span = ScreenLoadingManager.getActiveSpan(_activeNavigationSpanId);
|
|
751
|
+
if (span) {
|
|
752
|
+
// Update the span name from placeholder to actual screen name
|
|
753
|
+
span.screenName = currentRouteName;
|
|
754
|
+
// End the span - the native frame tracker will provide the actual render timestamp
|
|
755
|
+
ScreenLoadingManager.endSpan(_activeNavigationSpanId)
|
|
756
|
+
.then(() => {
|
|
757
|
+
Logger.log(`[ScreenLoading] Completed span for navigation to ${currentRouteName}`);
|
|
758
|
+
})
|
|
759
|
+
.catch((error) => {
|
|
760
|
+
Logger.warn('[ScreenLoading] Failed to end navigation span:', error);
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
// Clear the active span and timeout
|
|
764
|
+
_activeNavigationSpanId = null;
|
|
765
|
+
_clearStateChangeTimeout();
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
// Update the current route for the rest of Luciq's tracking
|
|
769
|
+
_currentRoute = currentRouteName;
|
|
770
|
+
// Report to native
|
|
771
|
+
NativeLuciq.reportScreenChange(currentRouteName, spanIdForReport);
|
|
772
|
+
};
|
|
650
773
|
export const onNavigationStateChange = (prevState, currentState, _action) => {
|
|
651
774
|
const currentScreen = LuciqUtils.getActiveRouteName(currentState);
|
|
652
775
|
const prevScreen = LuciqUtils.getActiveRouteName(prevState);
|
|
653
776
|
if (prevScreen !== currentScreen) {
|
|
777
|
+
// Start Screen Loading measurement for v4
|
|
778
|
+
let screenLoadingSpanId = null;
|
|
779
|
+
if (ScreenLoadingManager.isFeatureEnabled()) {
|
|
780
|
+
const span = ScreenLoadingManager.createSpan(currentScreen || 'Unknown', false);
|
|
781
|
+
if (span) {
|
|
782
|
+
screenLoadingSpanId = span.spanId;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
654
785
|
reportCurrentViewForAndroid(currentScreen);
|
|
655
786
|
if (_currentScreen != null && _currentScreen !== firstScreen) {
|
|
656
|
-
NativeLuciq.reportScreenChange(_currentScreen);
|
|
787
|
+
NativeLuciq.reportScreenChange(_currentScreen, screenLoadingSpanId);
|
|
657
788
|
_currentScreen = null;
|
|
658
789
|
}
|
|
659
790
|
_currentScreen = currentScreen;
|
|
660
791
|
setTimeout(() => {
|
|
661
792
|
if (currentScreen && _currentScreen === currentScreen) {
|
|
662
|
-
NativeLuciq.reportScreenChange(currentScreen);
|
|
793
|
+
NativeLuciq.reportScreenChange(currentScreen, screenLoadingSpanId);
|
|
663
794
|
_currentScreen = null;
|
|
664
795
|
}
|
|
796
|
+
// End Screen Loading measurement for v4
|
|
797
|
+
if (screenLoadingSpanId) {
|
|
798
|
+
ScreenLoadingManager.endSpan(screenLoadingSpanId).catch((error) => {
|
|
799
|
+
Logger.warn('[ScreenLoading] Failed to end span:', error);
|
|
800
|
+
});
|
|
801
|
+
}
|
|
665
802
|
}, 1000);
|
|
666
803
|
}
|
|
667
804
|
};
|
|
@@ -669,16 +806,25 @@ export const onStateChange = (state) => {
|
|
|
669
806
|
if (!state) {
|
|
670
807
|
return;
|
|
671
808
|
}
|
|
809
|
+
// Delegate to the new state change handler for Screen Loading
|
|
810
|
+
// This handles reportScreenChange when setNavigationListener was called
|
|
811
|
+
_onNavigationStateChange();
|
|
812
|
+
// When setNavigationListener is used, _onNavigationStateChange already handles
|
|
813
|
+
// reportScreenChange properly - skip legacy logic to avoid duplicate calls
|
|
814
|
+
if (_navigationRef?.current) {
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
// Fallback: Legacy screen tracking for users who only use onStateChange without setNavigationListener
|
|
672
818
|
const currentScreen = LuciqUtils.getFullRoute(state);
|
|
673
819
|
reportCurrentViewForAndroid(currentScreen);
|
|
674
820
|
if (_currentScreen !== null && _currentScreen !== firstScreen) {
|
|
675
|
-
NativeLuciq.reportScreenChange(_currentScreen);
|
|
821
|
+
NativeLuciq.reportScreenChange(_currentScreen, null);
|
|
676
822
|
_currentScreen = null;
|
|
677
823
|
}
|
|
678
824
|
_currentScreen = currentScreen;
|
|
679
825
|
setTimeout(() => {
|
|
680
826
|
if (_currentScreen === currentScreen) {
|
|
681
|
-
NativeLuciq.reportScreenChange(currentScreen);
|
|
827
|
+
NativeLuciq.reportScreenChange(currentScreen, null);
|
|
682
828
|
_currentScreen = null;
|
|
683
829
|
}
|
|
684
830
|
}, 1000);
|
|
@@ -689,12 +835,23 @@ export const onStateChange = (state) => {
|
|
|
689
835
|
*
|
|
690
836
|
*/
|
|
691
837
|
export const setNavigationListener = (navigationRef) => {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
838
|
+
// Store the navigationRef for Screen Loading tracking
|
|
839
|
+
_navigationRef = navigationRef;
|
|
840
|
+
if (!navigationRef?.current) {
|
|
841
|
+
Logger.warn('[Luciq] Navigation ref not available, cannot set listeners');
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
// Register the __unsafe_action__ listener for span creation
|
|
845
|
+
// This listener fires on navigation dispatch (start of navigation)
|
|
846
|
+
navigationRef.current.addListener('__unsafe_action__', _onNavigationAction);
|
|
847
|
+
// NOTE: We do NOT register a 'state' listener here because the user is expected
|
|
848
|
+
// to pass Luciq.onStateChange to NavigationContainer's onStateChange prop.
|
|
849
|
+
// Registering both would cause duplicate reportScreenChange calls.
|
|
850
|
+
Logger.log('[Luciq] Registered Screen Loading listener (__unsafe_action__)');
|
|
851
|
+
// return stateListener;
|
|
695
852
|
};
|
|
696
853
|
export const reportScreenChange = (screenName) => {
|
|
697
|
-
NativeLuciq.reportScreenChange(screenName);
|
|
854
|
+
NativeLuciq.reportScreenChange(screenName, null);
|
|
698
855
|
};
|
|
699
856
|
/**
|
|
700
857
|
* Add feature flags to the next report.
|
|
@@ -750,7 +907,7 @@ export const componentDidAppearListener = (event) => {
|
|
|
750
907
|
return;
|
|
751
908
|
}
|
|
752
909
|
if (_lastScreen !== event.componentName) {
|
|
753
|
-
NativeLuciq.reportScreenChange(event.componentName);
|
|
910
|
+
NativeLuciq.reportScreenChange(event.componentName, null);
|
|
754
911
|
_lastScreen = event.componentName;
|
|
755
912
|
}
|
|
756
913
|
};
|
|
@@ -3,6 +3,11 @@ import { NetworkData, ProgressCallback } from '../utils/XhrNetworkInterceptor';
|
|
|
3
3
|
import { NetworkListenerType } from '../native/NativeNetworkLogger';
|
|
4
4
|
export type { NetworkData };
|
|
5
5
|
export type NetworkDataObfuscationHandler = (data: NetworkData) => Promise<NetworkData>;
|
|
6
|
+
/**
|
|
7
|
+
* Sets whether network logs should be sent with bug reports.
|
|
8
|
+
* It is enabled by default.
|
|
9
|
+
* @param isEnabled
|
|
10
|
+
*/
|
|
6
11
|
export declare const setEnabled: (isEnabled: boolean) => void;
|
|
7
12
|
/**
|
|
8
13
|
* @internal
|
|
@@ -20,25 +20,21 @@ function getPortFromUrl(url) {
|
|
|
20
20
|
* It is enabled by default.
|
|
21
21
|
* @param isEnabled
|
|
22
22
|
*/
|
|
23
|
-
const NET_TAG = 'LCQ-RN-NET:';
|
|
24
23
|
export const setEnabled = (isEnabled) => {
|
|
25
24
|
if (isEnabled) {
|
|
26
25
|
xhr.enableInterception();
|
|
27
26
|
xhr.setOnDoneCallback(async (network) => {
|
|
28
|
-
Logger.debug(NET_TAG, `[NetworkLogger] onDoneCallback received: ${network.method} ${network.url}, status=${network.responseCode}`);
|
|
29
27
|
// eslint-disable-next-line no-new-func
|
|
30
28
|
const predicate = Function('network', 'return ' + _requestFilterExpression);
|
|
31
29
|
if (!predicate(network)) {
|
|
32
30
|
const MAX_NETWORK_BODY_SIZE_IN_BYTES = await NativeLuciq.getNetworkBodyMaxSize();
|
|
33
31
|
try {
|
|
34
32
|
if (_networkDataObfuscationHandler) {
|
|
35
|
-
Logger.debug(NET_TAG, `[NetworkLogger] Running obfuscation handler for ${network.url}`);
|
|
36
33
|
network = await _networkDataObfuscationHandler(network);
|
|
37
34
|
}
|
|
38
35
|
if (__DEV__) {
|
|
39
36
|
const urlPort = getPortFromUrl(network.url);
|
|
40
37
|
if (urlPort === LuciqRNConfig.metroDevServerPort) {
|
|
41
|
-
Logger.debug(NET_TAG, `[NetworkLogger] Skipping Metro dev server request: ${network.url}`);
|
|
42
38
|
return;
|
|
43
39
|
}
|
|
44
40
|
}
|
|
@@ -58,16 +54,12 @@ export const setEnabled = (isEnabled) => {
|
|
|
58
54
|
network.responseBody = `Body is omitted because content type ${network.contentType} isn't supported`;
|
|
59
55
|
Logger.warn(`LCQ-RN: The response body for the network request with URL ${network.url} has been omitted because the content type ${network.contentType} isn't supported.`);
|
|
60
56
|
}
|
|
61
|
-
Logger.debug(NET_TAG, `[NetworkLogger] Reporting network log to native: ${network.method} ${network.url}`);
|
|
62
57
|
reportNetworkLog(network);
|
|
63
58
|
}
|
|
64
59
|
catch (e) {
|
|
65
|
-
Logger.error(
|
|
60
|
+
Logger.error(e);
|
|
66
61
|
}
|
|
67
62
|
}
|
|
68
|
-
else {
|
|
69
|
-
Logger.debug(NET_TAG, `[NetworkLogger] Request filtered out by predicate: ${network.method} ${network.url}, expression="${_requestFilterExpression}"`);
|
|
70
|
-
}
|
|
71
63
|
});
|
|
72
64
|
}
|
|
73
65
|
else {
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export interface ScreenLoadingSpan {
|
|
2
|
+
spanId: string;
|
|
3
|
+
screenName: string;
|
|
4
|
+
startTimestamp: number;
|
|
5
|
+
endTimestamp?: number;
|
|
6
|
+
ttid?: number;
|
|
7
|
+
status: 'pending' | 'measuring' | 'completed' | 'error';
|
|
8
|
+
isManual: boolean;
|
|
9
|
+
attributes: Map<string, number>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Automatic Screen Loading Measurement
|
|
13
|
+
*
|
|
14
|
+
* Start point: `__unsafe_action__` navigation event (below).
|
|
15
|
+
* - Fires when a navigation action is dispatched, before the target screen mounts.
|
|
16
|
+
* - `ScreenLoadingManager.createSpan()` records `nowMicros()` as the span start.
|
|
17
|
+
*
|
|
18
|
+
* End point: `_onNavigationStateChange()` (called from `onStateChange`).
|
|
19
|
+
* - Fires after navigation state has settled and the new screen is mounted.
|
|
20
|
+
* - `ScreenLoadingManager.endSpan()` fetches the native frame timestamp from
|
|
21
|
+
* CADisplayLink (iOS) / Choreographer (Android) to mark actual render completion.
|
|
22
|
+
* - The TTID is: native frame timestamp − span start.
|
|
23
|
+
*
|
|
24
|
+
*
|
|
25
|
+
* Manual Screen Loading Measurement
|
|
26
|
+
*
|
|
27
|
+
* Start point: Component instantiation (lazy init block before first render).
|
|
28
|
+
* - `nowMicros()` is captured as `constructorTimestampRef` and passed to
|
|
29
|
+
* `ScreenLoadingManager.createSpan()` as the span's start timestamp.
|
|
30
|
+
*
|
|
31
|
+
* End point: `useLayoutEffect` (fires synchronously after React commits DOM
|
|
32
|
+
* mutations, before the browser paints).
|
|
33
|
+
* - `ScreenLoadingManager.endSpan()` fetches the native frame timestamp
|
|
34
|
+
* from CADisplayLink (iOS) / Choreographer (Android) to mark the actual
|
|
35
|
+
* render completion. The TTID is: native frame timestamp − span start.
|
|
36
|
+
*
|
|
37
|
+
* Both approaches share the same `endSpan()` path so TTID values are comparable.
|
|
38
|
+
*/
|
|
39
|
+
declare class ScreenLoadingManagerClass {
|
|
40
|
+
private activeSpans;
|
|
41
|
+
private isInitialized;
|
|
42
|
+
private isEnabled;
|
|
43
|
+
private isEndScreenLoadingEnabled;
|
|
44
|
+
private isFrameTrackingInitialized;
|
|
45
|
+
private activeSpanId;
|
|
46
|
+
private maxConcurrentSpans;
|
|
47
|
+
private excludedRoutes;
|
|
48
|
+
initialize(): Promise<void>;
|
|
49
|
+
refreshFlags(): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Exclude specific routes from automatic screen loading measurement
|
|
52
|
+
* @param routes Array of route names to exclude
|
|
53
|
+
*/
|
|
54
|
+
excludeRoutes(routes: string[]): void;
|
|
55
|
+
/**
|
|
56
|
+
* Include previously excluded routes back into screen loading measurement
|
|
57
|
+
* @param routes Array of route names to include (or empty to clear all exclusions)
|
|
58
|
+
*/
|
|
59
|
+
includeRoutes(routes?: string[]): void;
|
|
60
|
+
/**
|
|
61
|
+
* Check if a route is excluded from measurement
|
|
62
|
+
*/
|
|
63
|
+
isRouteExcluded(routeName: string): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Create a new screen loading span
|
|
66
|
+
* @param screenName Name of the screen
|
|
67
|
+
* @param isManual Whether the span is manual (not automatically created)
|
|
68
|
+
* @param startTimestampParam Optional start timestamp in microseconds (defaults to nowMicros())
|
|
69
|
+
* @returns The created span or null if the feature is not enabled
|
|
70
|
+
*/
|
|
71
|
+
createSpan(screenName: string, isManual?: boolean, startTimestampParam?: number): ScreenLoadingSpan | null;
|
|
72
|
+
/**
|
|
73
|
+
* End a screen loading span
|
|
74
|
+
* @param spanId The ID of the span to end
|
|
75
|
+
*/
|
|
76
|
+
endSpan(spanId: string): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Log a screen loading span
|
|
79
|
+
* @param span The span to log
|
|
80
|
+
*/
|
|
81
|
+
private logScreenLoading;
|
|
82
|
+
/**
|
|
83
|
+
* End a screen loading span using the current timestamp and active span ID
|
|
84
|
+
*/
|
|
85
|
+
endScreenLoading(): void;
|
|
86
|
+
/**
|
|
87
|
+
* Discard a span without logging or syncing it to native.
|
|
88
|
+
* Used when a span should be silently dropped (e.g., excluded route resolved after creation).
|
|
89
|
+
*/
|
|
90
|
+
discardSpan(spanId: string): void;
|
|
91
|
+
getActiveSpan(spanId: string): ScreenLoadingSpan | undefined;
|
|
92
|
+
getAllActiveSpans(): ScreenLoadingSpan[];
|
|
93
|
+
addSpanAttribute(spanId: string, key: string, value: number): void;
|
|
94
|
+
private cleanupOldestSpans;
|
|
95
|
+
isFeatureEnabled(): boolean;
|
|
96
|
+
isEndScreenLoadingFeatureEnabled(): boolean;
|
|
97
|
+
}
|
|
98
|
+
export declare const ScreenLoadingManager: ScreenLoadingManagerClass;
|
|
99
|
+
export {};
|