@luciq/react-native 19.4.0 → 19.6.0-51917-SNAPSHOT
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 +193 -10
- package/android/src/main/java/ai/luciq/reactlibrary/RNLuciqNetworkLoggerModule.java +29 -7
- package/android/src/main/java/ai/luciq/reactlibrary/RNLuciqReactnativeModule.java +36 -12
- package/android/src/main/java/ai/luciq/reactlibrary/utils/EventEmitterModule.java +7 -0
- 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 +179 -12
- package/dist/modules/NetworkLogger.d.ts +0 -5
- package/dist/modules/NetworkLogger.js +9 -1
- 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 +6 -0
- package/dist/utils/FeatureFlags.js +35 -0
- package/dist/utils/LuciqUtils.d.ts +25 -0
- package/dist/utils/LuciqUtils.js +50 -0
- package/dist/utils/RouteMatcher.d.ts +30 -0
- package/dist/utils/RouteMatcher.js +67 -0
- package/dist/utils/XhrNetworkInterceptor.js +85 -53
- 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 -2
- 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 +210 -12
- package/src/modules/NetworkLogger.ts +26 -1
- 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 +44 -0
- package/src/utils/LuciqUtils.ts +64 -0
- package/src/utils/RouteMatcher.ts +83 -0
- package/src/utils/XhrNetworkInterceptor.ts +128 -55
|
@@ -32,6 +32,8 @@ import java.util.concurrent.ConcurrentHashMap;
|
|
|
32
32
|
|
|
33
33
|
public class RNLuciqNetworkLoggerModule extends EventEmitterModule {
|
|
34
34
|
|
|
35
|
+
private static final String NET_TAG = "LCQ-RN-NET";
|
|
36
|
+
|
|
35
37
|
public final ConcurrentHashMap<String, OnCompleteCallback<NetworkLogSnapshot>> callbackMap = new ConcurrentHashMap<String, OnCompleteCallback<NetworkLogSnapshot>>();
|
|
36
38
|
|
|
37
39
|
public RNLuciqNetworkLoggerModule(ReactApplicationContext reactContext) {
|
|
@@ -57,7 +59,9 @@ public class RNLuciqNetworkLoggerModule extends EventEmitterModule {
|
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
private boolean getFlagValue(String key) {
|
|
60
|
-
|
|
62
|
+
boolean value = InternalAPM._isFeatureEnabledCP(key, "");
|
|
63
|
+
Log.d(NET_TAG, "[getFlagValue] key=" + key + ", value=" + value);
|
|
64
|
+
return value;
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
private WritableMap convertFromMapToWritableMap(Map<String, Object> map) {
|
|
@@ -86,14 +90,18 @@ public class RNLuciqNetworkLoggerModule extends EventEmitterModule {
|
|
|
86
90
|
*/
|
|
87
91
|
@ReactMethod
|
|
88
92
|
public void isNativeInterceptionEnabled(Promise promise) {
|
|
93
|
+
Log.d(NET_TAG, "[isNativeInterceptionEnabled] Querying CP_NATIVE_INTERCEPTION_ENABLED flag");
|
|
89
94
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
90
95
|
@Override
|
|
91
96
|
public void run() {
|
|
92
97
|
try {
|
|
93
|
-
|
|
98
|
+
boolean enabled = getFlagValue(CP_NATIVE_INTERCEPTION_ENABLED);
|
|
99
|
+
Log.d(NET_TAG, "[isNativeInterceptionEnabled] Result=" + enabled);
|
|
100
|
+
promise.resolve(enabled);
|
|
94
101
|
} catch (Exception e) {
|
|
102
|
+
Log.e(NET_TAG, "[isNativeInterceptionEnabled] Error — falling back to false (JS interceptor)", e);
|
|
95
103
|
e.printStackTrace();
|
|
96
|
-
promise.resolve(false);
|
|
104
|
+
promise.resolve(false);
|
|
97
105
|
}
|
|
98
106
|
|
|
99
107
|
}
|
|
@@ -107,14 +115,18 @@ public class RNLuciqNetworkLoggerModule extends EventEmitterModule {
|
|
|
107
115
|
*/
|
|
108
116
|
@ReactMethod
|
|
109
117
|
public void hasAPMNetworkPlugin(Promise promise) {
|
|
118
|
+
Log.d(NET_TAG, "[hasAPMNetworkPlugin] Querying APM_NETWORK_PLUGIN_INSTALLED flag");
|
|
110
119
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
111
120
|
@Override
|
|
112
121
|
public void run() {
|
|
113
122
|
try {
|
|
114
|
-
|
|
123
|
+
boolean hasPlugin = getFlagValue(APM_NETWORK_PLUGIN_INSTALLED);
|
|
124
|
+
Log.d(NET_TAG, "[hasAPMNetworkPlugin] Result=" + hasPlugin);
|
|
125
|
+
promise.resolve(hasPlugin);
|
|
115
126
|
} catch (Exception e) {
|
|
127
|
+
Log.e(NET_TAG, "[hasAPMNetworkPlugin] Error — falling back to false", e);
|
|
116
128
|
e.printStackTrace();
|
|
117
|
-
promise.resolve(false);
|
|
129
|
+
promise.resolve(false);
|
|
118
130
|
}
|
|
119
131
|
|
|
120
132
|
}
|
|
@@ -124,12 +136,14 @@ public class RNLuciqNetworkLoggerModule extends EventEmitterModule {
|
|
|
124
136
|
|
|
125
137
|
@ReactMethod
|
|
126
138
|
public void registerNetworkLogsListener() {
|
|
139
|
+
Log.d(NET_TAG, "[registerNetworkLogsListener] Registering network log sanitizer");
|
|
127
140
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
128
141
|
@Override
|
|
129
142
|
public void run() {
|
|
130
143
|
InternalAPM._registerNetworkLogSanitizer((networkLogSnapshot, onCompleteCallback) -> {
|
|
131
144
|
final String id = String.valueOf(onCompleteCallback.hashCode());
|
|
132
145
|
callbackMap.put(id, onCompleteCallback);
|
|
146
|
+
Log.d(NET_TAG, "[NetworkLogSanitizer] Received snapshot — id=" + id + ", url=" + networkLogSnapshot.getUrl() + ", responseCode=" + networkLogSnapshot.getResponseCode() + ", callbackMapSize=" + callbackMap.size());
|
|
133
147
|
|
|
134
148
|
WritableMap networkSnapshotParams = Arguments.createMap();
|
|
135
149
|
networkSnapshotParams.putString("id", id);
|
|
@@ -147,6 +161,7 @@ public class RNLuciqNetworkLoggerModule extends EventEmitterModule {
|
|
|
147
161
|
}
|
|
148
162
|
|
|
149
163
|
sendEvent(Constants.LCQ_NETWORK_LOGGER_HANDLER, networkSnapshotParams);
|
|
164
|
+
Log.d(NET_TAG, "[NetworkLogSanitizer] Sent event to JS: " + Constants.LCQ_NETWORK_LOGGER_HANDLER + " for " + networkLogSnapshot.getUrl());
|
|
150
165
|
});
|
|
151
166
|
}
|
|
152
167
|
});
|
|
@@ -154,10 +169,12 @@ public class RNLuciqNetworkLoggerModule extends EventEmitterModule {
|
|
|
154
169
|
|
|
155
170
|
@ReactMethod
|
|
156
171
|
public void resetNetworkLogsListener() {
|
|
172
|
+
Log.d(NET_TAG, "[resetNetworkLogsListener] Clearing network log sanitizer, callbackMapSize=" + callbackMap.size());
|
|
157
173
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
158
174
|
@Override
|
|
159
175
|
public void run() {
|
|
160
176
|
InternalAPM._registerNetworkLogSanitizer(null);
|
|
177
|
+
Log.d(NET_TAG, "[resetNetworkLogsListener] Sanitizer cleared");
|
|
161
178
|
}
|
|
162
179
|
});
|
|
163
180
|
}
|
|
@@ -172,23 +189,28 @@ public class RNLuciqNetworkLoggerModule extends EventEmitterModule {
|
|
|
172
189
|
ReadableMap requestHeaders,
|
|
173
190
|
ReadableMap responseHeaders
|
|
174
191
|
) {
|
|
192
|
+
Log.d(NET_TAG, "[updateNetworkLogSnapshot] callbackID=" + callbackID + ", url=" + url + ", responseCode=" + responseCode + ", callbackMapSize=" + callbackMap.size());
|
|
175
193
|
try {
|
|
176
|
-
// Convert ReadableMap to a Java Map for easier handling
|
|
177
194
|
Map<String, Object> requestHeadersMap = convertReadableMapToMap(requestHeaders);
|
|
178
195
|
Map<String, Object> responseHeadersMap = convertReadableMapToMap(responseHeaders);
|
|
179
196
|
|
|
180
197
|
NetworkLogSnapshot modifiedSnapshot = null;
|
|
181
198
|
if (!url.isEmpty()) {
|
|
182
199
|
modifiedSnapshot = new NetworkLogSnapshot(url, requestHeadersMap, requestBody, responseHeadersMap, responseBody, responseCode);
|
|
200
|
+
} else {
|
|
201
|
+
Log.d(NET_TAG, "[updateNetworkLogSnapshot] Empty URL — snapshot will be null (request filtered/removed)");
|
|
183
202
|
}
|
|
184
203
|
|
|
185
204
|
final OnCompleteCallback<NetworkLogSnapshot> callback = callbackMap.get(callbackID);
|
|
186
205
|
if (callback != null) {
|
|
187
206
|
callback.onComplete(modifiedSnapshot);
|
|
188
207
|
callbackMap.remove(callbackID);
|
|
208
|
+
Log.d(NET_TAG, "[updateNetworkLogSnapshot] Callback invoked and removed for " + callbackID + ", remaining=" + callbackMap.size());
|
|
209
|
+
} else {
|
|
210
|
+
Log.e(NET_TAG, "[updateNetworkLogSnapshot] No callback found for callbackID=" + callbackID + " — possible leak or duplicate call, mapKeys=" + callbackMap.keySet());
|
|
189
211
|
}
|
|
190
212
|
} catch (Exception e) {
|
|
191
|
-
|
|
213
|
+
Log.e(NET_TAG, "[updateNetworkLogSnapshot] Exception processing snapshot: " + e.getMessage() + " for callbackID=" + callbackID, e);
|
|
192
214
|
Log.e("IB-CP-Bridge", "LuciqNetworkLogger.updateNetworkLogSnapshot failed to parse the network snapshot object.");
|
|
193
215
|
}
|
|
194
216
|
}
|
|
@@ -86,6 +86,7 @@ import javax.annotation.Nullable;
|
|
|
86
86
|
public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
87
87
|
|
|
88
88
|
private static final String TAG = "Luciq-RN-Core";
|
|
89
|
+
private static final String NET_TAG = "LCQ-RN-NET";
|
|
89
90
|
|
|
90
91
|
private LuciqCustomTextPlaceHolder placeHolders;
|
|
91
92
|
private static Report currentReport;
|
|
@@ -163,6 +164,7 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
163
164
|
|
|
164
165
|
|
|
165
166
|
) {
|
|
167
|
+
Log.d(NET_TAG, "[init] Called — logLevel=" + logLevel + ", useNativeNetworkInterception=" + useNativeNetworkInterception + ", codePushVersion=" + codePushVersion + ", appVariant=" + appVariant);
|
|
166
168
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
167
169
|
@Override
|
|
168
170
|
public void run() {
|
|
@@ -204,6 +206,7 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
204
206
|
}
|
|
205
207
|
|
|
206
208
|
builder.build();
|
|
209
|
+
Log.d(NET_TAG, "[init] SDK build complete");
|
|
207
210
|
}
|
|
208
211
|
});
|
|
209
212
|
}
|
|
@@ -969,6 +972,7 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
969
972
|
final String requestHeaders,
|
|
970
973
|
final String responseHeaders,
|
|
971
974
|
final double duration) {
|
|
975
|
+
Log.d(NET_TAG, "[networkLogAndroid-Core] Received from JS: " + method + " " + url + ", status=" + (int) responseCode + ", duration=" + (long) duration + "ms, reqBodyLen=" + (requestBody != null ? requestBody.length() : 0) + ", resBodyLen=" + (responseBody != null ? responseBody.length() : 0));
|
|
972
976
|
try {
|
|
973
977
|
final String date = String.valueOf(System.currentTimeMillis());
|
|
974
978
|
|
|
@@ -985,11 +989,14 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
985
989
|
networkLog.setRequestHeaders(requestHeaders);
|
|
986
990
|
networkLog.setResponseHeaders(responseHeaders);
|
|
987
991
|
} catch (OutOfMemoryError | Exception exception) {
|
|
992
|
+
Log.e(NET_TAG, "[networkLogAndroid-Core] OOM/Error setting log contents: " + exception.getMessage() + " for " + method + " " + url);
|
|
988
993
|
Log.d(TAG, "Error: " + exception.getMessage() + "while trying to set network log contents (request body, response body, request headers, and response headers).");
|
|
989
994
|
}
|
|
990
995
|
|
|
991
996
|
networkLog.insert();
|
|
997
|
+
Log.d(NET_TAG, "[networkLogAndroid-Core] Successfully inserted NetworkLog: " + method + " " + url);
|
|
992
998
|
} catch (OutOfMemoryError | Exception exception) {
|
|
999
|
+
Log.e(NET_TAG, "[networkLogAndroid-Core] OOM/Error inserting network log: " + exception.getMessage() + " for " + method + " " + url);
|
|
993
1000
|
Log.d(TAG, "Error: " + exception.getMessage() + "while trying to insert a network log");
|
|
994
1001
|
}
|
|
995
1002
|
}
|
|
@@ -1078,16 +1085,18 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1078
1085
|
* Reports that the screen has been changed (Repro Steps) the screen sent to this method will be the 'current view' on the dashboard
|
|
1079
1086
|
*
|
|
1080
1087
|
* @param screenName string containing the screen name
|
|
1088
|
+
* @param spanId the span ID for screen loading tracking (nullable)
|
|
1081
1089
|
*/
|
|
1082
1090
|
@ReactMethod
|
|
1083
|
-
public void reportScreenChange(final String screenName) {
|
|
1091
|
+
public void reportScreenChange(final String screenName, @Nullable final String spanId) {
|
|
1084
1092
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
1085
1093
|
@Override
|
|
1086
1094
|
public void run() {
|
|
1087
1095
|
try {
|
|
1088
|
-
|
|
1096
|
+
Long uiTraceId = spanId != null ? Long.parseLong(spanId) : null;
|
|
1097
|
+
Method method = getMethod(Class.forName("ai.luciq.library.Luciq"), "reportScreenChange", Bitmap.class, String.class , Long.class);
|
|
1089
1098
|
if (method != null) {
|
|
1090
|
-
method.invoke(null, null, screenName);
|
|
1099
|
+
method.invoke(null, null, screenName , uiTraceId);
|
|
1091
1100
|
}
|
|
1092
1101
|
} catch (Exception e) {
|
|
1093
1102
|
e.printStackTrace();
|
|
@@ -1170,7 +1179,7 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1170
1179
|
*/
|
|
1171
1180
|
@ReactMethod
|
|
1172
1181
|
public void registerFeatureFlagsChangeListener() {
|
|
1173
|
-
|
|
1182
|
+
Log.d(NET_TAG, "[registerFeatureFlagsChangeListener] Registering native feature flags listener");
|
|
1174
1183
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
1175
1184
|
@Override
|
|
1176
1185
|
public void run() {
|
|
@@ -1178,6 +1187,7 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1178
1187
|
InternalCore.INSTANCE._setFeaturesStateListener(new FeaturesStateListener() {
|
|
1179
1188
|
@Override
|
|
1180
1189
|
public void invoke(@NonNull CoreFeaturesState featuresState) {
|
|
1190
|
+
Log.d(NET_TAG, "[FeatureFlagsListener] Received update — W3CTraceID=" + featuresState.isW3CExternalTraceIdEnabled() + ", generatedHeader=" + featuresState.isAttachingGeneratedHeaderEnabled() + ", caughtHeader=" + featuresState.isAttachingCapturedHeaderEnabled() + ", networkBodyLimit=" + featuresState.getNetworkLogCharLimit());
|
|
1181
1191
|
WritableMap params = Arguments.createMap();
|
|
1182
1192
|
params.putBoolean("isW3ExternalTraceIDEnabled", featuresState.isW3CExternalTraceIdEnabled());
|
|
1183
1193
|
params.putBoolean("isW3ExternalGeneratedHeaderEnabled", featuresState.isAttachingGeneratedHeaderEnabled());
|
|
@@ -1185,9 +1195,11 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1185
1195
|
params.putInt("networkBodyLimit",featuresState.getNetworkLogCharLimit());
|
|
1186
1196
|
|
|
1187
1197
|
sendEvent(Constants.LCQ_ON_FEATURE_FLAGS_UPDATE_RECEIVED_CALLBACK, params);
|
|
1198
|
+
Log.d(NET_TAG, "[FeatureFlagsListener] Sent event to JS: " + Constants.LCQ_ON_FEATURE_FLAGS_UPDATE_RECEIVED_CALLBACK);
|
|
1188
1199
|
}
|
|
1189
1200
|
});
|
|
1190
1201
|
} catch (Exception e) {
|
|
1202
|
+
Log.e(NET_TAG, "[registerFeatureFlagsChangeListener] Failed to register listener", e);
|
|
1191
1203
|
e.printStackTrace();
|
|
1192
1204
|
}
|
|
1193
1205
|
|
|
@@ -1202,13 +1214,16 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1202
1214
|
*/
|
|
1203
1215
|
@ReactMethod
|
|
1204
1216
|
public void isW3ExternalTraceIDEnabled(Promise promise) {
|
|
1205
|
-
|
|
1217
|
+
Log.d(NET_TAG, "[isW3ExternalTraceIDEnabled] Querying native flag");
|
|
1206
1218
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
1207
1219
|
@Override
|
|
1208
1220
|
public void run() {
|
|
1209
1221
|
try {
|
|
1210
|
-
|
|
1222
|
+
boolean enabled = InternalCore.INSTANCE._isFeatureEnabled(CoreFeature.W3C_EXTERNAL_TRACE_ID);
|
|
1223
|
+
Log.d(NET_TAG, "[isW3ExternalTraceIDEnabled] Result=" + enabled);
|
|
1224
|
+
promise.resolve(enabled);
|
|
1211
1225
|
} catch (Exception e) {
|
|
1226
|
+
Log.e(NET_TAG, "[isW3ExternalTraceIDEnabled] Error querying flag", e);
|
|
1212
1227
|
e.printStackTrace();
|
|
1213
1228
|
promise.resolve(false);
|
|
1214
1229
|
}
|
|
@@ -1224,13 +1239,16 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1224
1239
|
*/
|
|
1225
1240
|
@ReactMethod
|
|
1226
1241
|
public void isW3ExternalGeneratedHeaderEnabled(Promise promise) {
|
|
1227
|
-
|
|
1242
|
+
Log.d(NET_TAG, "[isW3ExternalGeneratedHeaderEnabled] Querying native flag");
|
|
1228
1243
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
1229
1244
|
@Override
|
|
1230
1245
|
public void run() {
|
|
1231
1246
|
try {
|
|
1232
|
-
|
|
1247
|
+
boolean enabled = InternalCore.INSTANCE._isFeatureEnabled(CoreFeature.W3C_ATTACHING_GENERATED_HEADER);
|
|
1248
|
+
Log.d(NET_TAG, "[isW3ExternalGeneratedHeaderEnabled] Result=" + enabled);
|
|
1249
|
+
promise.resolve(enabled);
|
|
1233
1250
|
} catch (Exception e) {
|
|
1251
|
+
Log.e(NET_TAG, "[isW3ExternalGeneratedHeaderEnabled] Error querying flag", e);
|
|
1234
1252
|
e.printStackTrace();
|
|
1235
1253
|
promise.resolve(false);
|
|
1236
1254
|
}
|
|
@@ -1245,13 +1263,16 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1245
1263
|
*/
|
|
1246
1264
|
@ReactMethod
|
|
1247
1265
|
public void isW3CaughtHeaderEnabled(Promise promise) {
|
|
1248
|
-
|
|
1266
|
+
Log.d(NET_TAG, "[isW3CaughtHeaderEnabled] Querying native flag");
|
|
1249
1267
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
1250
1268
|
@Override
|
|
1251
1269
|
public void run() {
|
|
1252
1270
|
try {
|
|
1253
|
-
|
|
1271
|
+
boolean enabled = InternalCore.INSTANCE._isFeatureEnabled(CoreFeature.W3C_ATTACHING_CAPTURED_HEADER);
|
|
1272
|
+
Log.d(NET_TAG, "[isW3CaughtHeaderEnabled] Result=" + enabled);
|
|
1273
|
+
promise.resolve(enabled);
|
|
1254
1274
|
} catch (Exception e) {
|
|
1275
|
+
Log.e(NET_TAG, "[isW3CaughtHeaderEnabled] Error querying flag", e);
|
|
1255
1276
|
e.printStackTrace();
|
|
1256
1277
|
promise.resolve(false);
|
|
1257
1278
|
}
|
|
@@ -1345,13 +1366,16 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1345
1366
|
*/
|
|
1346
1367
|
@ReactMethod
|
|
1347
1368
|
public void getNetworkBodyMaxSize(Promise promise) {
|
|
1348
|
-
|
|
1369
|
+
Log.d(NET_TAG, "[getNetworkBodyMaxSize] Querying network body size limit");
|
|
1349
1370
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
1350
1371
|
@Override
|
|
1351
1372
|
public void run() {
|
|
1352
1373
|
try {
|
|
1353
|
-
|
|
1374
|
+
Object limit = InternalCore.INSTANCE.get_networkLogCharLimit();
|
|
1375
|
+
Log.d(NET_TAG, "[getNetworkBodyMaxSize] Result=" + limit);
|
|
1376
|
+
promise.resolve(limit);
|
|
1354
1377
|
} catch (Exception e) {
|
|
1378
|
+
Log.e(NET_TAG, "[getNetworkBodyMaxSize] Error querying limit", e);
|
|
1355
1379
|
e.printStackTrace();
|
|
1356
1380
|
promise.resolve(false);
|
|
1357
1381
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
package ai.luciq.reactlibrary.utils;
|
|
2
2
|
|
|
3
|
+
import android.util.Log;
|
|
4
|
+
|
|
3
5
|
import androidx.annotation.Nullable;
|
|
4
6
|
import androidx.annotation.VisibleForTesting;
|
|
5
7
|
|
|
@@ -10,6 +12,7 @@ import com.facebook.react.bridge.WritableMap;
|
|
|
10
12
|
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
11
13
|
|
|
12
14
|
public abstract class EventEmitterModule extends ReactContextBaseJavaModule {
|
|
15
|
+
private static final String NET_TAG = "LCQ-RN-NET";
|
|
13
16
|
private int listenerCount = 0;
|
|
14
17
|
|
|
15
18
|
public EventEmitterModule(ReactApplicationContext context) {
|
|
@@ -22,14 +25,18 @@ public abstract class EventEmitterModule extends ReactContextBaseJavaModule {
|
|
|
22
25
|
getReactApplicationContext()
|
|
23
26
|
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
24
27
|
.emit(event, params);
|
|
28
|
+
} else {
|
|
29
|
+
Log.w(NET_TAG, "[EventEmitter] Event DROPPED (no JS listeners): event=" + event + ", module=" + getName() + ", listenerCount=0");
|
|
25
30
|
}
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
protected void addListener(String ignoredEvent) {
|
|
29
34
|
listenerCount++;
|
|
35
|
+
Log.d(NET_TAG, "[EventEmitter] addListener — module=" + getName() + ", event=" + ignoredEvent + ", listenerCount=" + listenerCount);
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
protected void removeListeners(Integer count) {
|
|
33
39
|
listenerCount -= count;
|
|
40
|
+
Log.d(NET_TAG, "[EventEmitter] removeListeners — module=" + getName() + ", removed=" + count + ", listenerCount=" + listenerCount);
|
|
34
41
|
}
|
|
35
42
|
}
|
|
@@ -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;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect, useLayoutEffect, useContext } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { ScreenLoadingManager } from '../modules/apm/ScreenLoadingManager';
|
|
4
|
+
import { Logger } from '../utils/logger';
|
|
5
|
+
import { nowMicros, toEpochMicros } from '../utils/LuciqUtils';
|
|
6
|
+
// Context to handle nested components
|
|
7
|
+
const ScreenLoadingContext = React.createContext(false);
|
|
8
|
+
export function LuciqCaptureScreenLoading(props) {
|
|
9
|
+
const { screenName, record, onMeasured, onLayout, children, ...viewProps } = props;
|
|
10
|
+
const isNested = useContext(ScreenLoadingContext);
|
|
11
|
+
// Refs for timestamps (these don't need to trigger re-renders)
|
|
12
|
+
const constructorTimestampRef = useRef(nowMicros()); // microseconds
|
|
13
|
+
const renderStartTimestampRef = useRef(undefined);
|
|
14
|
+
const renderEndTimestampRef = useRef(undefined);
|
|
15
|
+
const mountTimestampRef = useRef(undefined);
|
|
16
|
+
// Guards to ensure single execution
|
|
17
|
+
const initializedRef = useRef(false);
|
|
18
|
+
const hasFirstRenderCompletedRef = useRef(false);
|
|
19
|
+
const attributesRecordedRef = useRef(false);
|
|
20
|
+
const initialSpanIdRef = useRef(null);
|
|
21
|
+
// Capture render start timestamp ONLY on first render
|
|
22
|
+
if (!hasFirstRenderCompletedRef.current) {
|
|
23
|
+
renderStartTimestampRef.current = nowMicros();
|
|
24
|
+
}
|
|
25
|
+
// Initialize span - runs once like constructor (lazy initialization)
|
|
26
|
+
if (!initializedRef.current) {
|
|
27
|
+
initializedRef.current = true;
|
|
28
|
+
// Initialize span if conditions are met
|
|
29
|
+
try {
|
|
30
|
+
if (record !== false && ScreenLoadingManager.isFeatureEnabled()) {
|
|
31
|
+
const span = ScreenLoadingManager.createSpan(screenName, true, constructorTimestampRef.current);
|
|
32
|
+
if (span) {
|
|
33
|
+
initialSpanIdRef.current = span.spanId;
|
|
34
|
+
Logger.log(`[LuciqScreenLoading] Span ${span.spanId} created in constructor`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
Logger.error('[LuciqScreenLoading] Failed to create span:', error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const [spanId, setSpanId] = useState(initialSpanIdRef.current);
|
|
43
|
+
const [isMeasured, setIsMeasured] = useState(false);
|
|
44
|
+
// Ref to avoid stale closure in useLayoutEffect
|
|
45
|
+
const onMeasuredRef = useRef(onMeasured);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
onMeasuredRef.current = onMeasured;
|
|
48
|
+
}, [onMeasured]);
|
|
49
|
+
// Refs to track latest values for cleanup (componentWillUnmount)
|
|
50
|
+
const spanIdRef = useRef(spanId);
|
|
51
|
+
const isMeasuredRef = useRef(isMeasured);
|
|
52
|
+
// Keep refs in sync with state
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
spanIdRef.current = spanId;
|
|
55
|
+
}, [spanId]);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
isMeasuredRef.current = isMeasured;
|
|
58
|
+
}, [isMeasured]);
|
|
59
|
+
// Handle nested component detection
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
// Check if we're nested and should ignore this component
|
|
62
|
+
if (isNested && initialSpanIdRef.current) {
|
|
63
|
+
Logger.log(`[LuciqScreenLoading] Nested component detected, ignoring span ${initialSpanIdRef.current}`);
|
|
64
|
+
// Cancel the span
|
|
65
|
+
setSpanId(null);
|
|
66
|
+
}
|
|
67
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
68
|
+
}, []); // Empty deps = componentDidMount
|
|
69
|
+
// Record lifecycle timestamps after first render completes (synchronous)
|
|
70
|
+
// useLayoutEffect fires synchronously after DOM mutations but before browser paint
|
|
71
|
+
useLayoutEffect(() => {
|
|
72
|
+
// Skip if no span, already recorded, or nested
|
|
73
|
+
if (!spanId || attributesRecordedRef.current || isNested) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// endSpan is async (native frame timestamp fetch), fire-and-forget from useLayoutEffect
|
|
77
|
+
ScreenLoadingManager.endSpan(spanId)
|
|
78
|
+
.then(() => {
|
|
79
|
+
const completedSpan = ScreenLoadingManager.getActiveSpan(spanId);
|
|
80
|
+
if (completedSpan?.ttid && onMeasuredRef.current) {
|
|
81
|
+
onMeasuredRef.current(completedSpan.ttid / 1000);
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
.catch((error) => {
|
|
85
|
+
Logger.warn('[LuciqScreenLoading] Failed to end span:', error);
|
|
86
|
+
});
|
|
87
|
+
attributesRecordedRef.current = true;
|
|
88
|
+
mountTimestampRef.current = nowMicros();
|
|
89
|
+
try {
|
|
90
|
+
// Record all timestamps
|
|
91
|
+
ScreenLoadingManager.addSpanAttribute(spanId, 'cnst_mus_st', toEpochMicros(constructorTimestampRef.current));
|
|
92
|
+
if (renderStartTimestampRef.current) {
|
|
93
|
+
ScreenLoadingManager.addSpanAttribute(spanId, 'rnd_mus_st', toEpochMicros(renderStartTimestampRef.current));
|
|
94
|
+
}
|
|
95
|
+
ScreenLoadingManager.addSpanAttribute(spanId, 'mnt_mus_st', toEpochMicros(mountTimestampRef.current));
|
|
96
|
+
// Record all durations
|
|
97
|
+
if (renderStartTimestampRef.current) {
|
|
98
|
+
// Constructor duration: time from component init to first render start
|
|
99
|
+
const constructorDuration = renderStartTimestampRef.current - constructorTimestampRef.current;
|
|
100
|
+
ScreenLoadingManager.addSpanAttribute(spanId, 'cnst_mus', constructorDuration);
|
|
101
|
+
}
|
|
102
|
+
if (renderEndTimestampRef.current && renderStartTimestampRef.current) {
|
|
103
|
+
// Render duration: time spent creating JSX
|
|
104
|
+
const renderDuration = renderEndTimestampRef.current - renderStartTimestampRef.current;
|
|
105
|
+
ScreenLoadingManager.addSpanAttribute(spanId, 'rnd_mus', renderDuration);
|
|
106
|
+
}
|
|
107
|
+
if (mountTimestampRef.current && renderEndTimestampRef.current) {
|
|
108
|
+
// Mount duration: time from render complete to effect execution
|
|
109
|
+
const mountDuration = mountTimestampRef.current - renderEndTimestampRef.current;
|
|
110
|
+
ScreenLoadingManager.addSpanAttribute(spanId, 'mnt_mus', mountDuration);
|
|
111
|
+
}
|
|
112
|
+
Logger.log(`[LuciqScreenLoading] Lifecycle measurements for span ${spanId}:`, {
|
|
113
|
+
constructor_us: renderStartTimestampRef.current
|
|
114
|
+
? renderStartTimestampRef.current - constructorTimestampRef.current
|
|
115
|
+
: undefined,
|
|
116
|
+
render_us: renderEndTimestampRef.current && renderStartTimestampRef.current
|
|
117
|
+
? renderEndTimestampRef.current - renderStartTimestampRef.current
|
|
118
|
+
: undefined,
|
|
119
|
+
mount_us: mountTimestampRef.current && renderEndTimestampRef.current
|
|
120
|
+
? mountTimestampRef.current - renderEndTimestampRef.current
|
|
121
|
+
: undefined,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
Logger.error(`[LuciqScreenLoading] Failed to record attributes for span ${spanId}:`, error);
|
|
126
|
+
}
|
|
127
|
+
// End the span — mark as measured synchronously to guard against unmount race
|
|
128
|
+
setIsMeasured(true);
|
|
129
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
130
|
+
}, [spanId]); // Run when spanId is set
|
|
131
|
+
// componentWillUnmount equivalent
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
return () => {
|
|
134
|
+
// Cleanup on unmount if not measured
|
|
135
|
+
if (spanIdRef.current && !isMeasuredRef.current) {
|
|
136
|
+
ScreenLoadingManager.endSpan(spanIdRef.current).catch((error) => {
|
|
137
|
+
Logger.warn('[LuciqScreenLoading] Failed to end span on unmount:', error);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}, []); // Empty deps = only runs cleanup on unmount
|
|
142
|
+
// Create the JSX result
|
|
143
|
+
const result = (<ScreenLoadingContext.Provider value={spanId !== null}>
|
|
144
|
+
<View {...viewProps} onLayout={onLayout}>
|
|
145
|
+
{children}
|
|
146
|
+
</View>
|
|
147
|
+
</ScreenLoadingContext.Provider>);
|
|
148
|
+
// Capture render end timestamp ONLY on first render (after JSX creation)
|
|
149
|
+
if (!hasFirstRenderCompletedRef.current) {
|
|
150
|
+
renderEndTimestampRef.current = nowMicros();
|
|
151
|
+
hasFirstRenderCompletedRef.current = true;
|
|
152
|
+
}
|
|
153
|
+
return result;
|
|
154
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -18,4 +18,6 @@ import type { SessionMetadata } from './models/SessionMetadata';
|
|
|
18
18
|
export * from './utils/Enums';
|
|
19
19
|
export { Report, CustomSpan, APM, BugReporting, CrashReporting, FeatureRequests, NetworkLogger, SessionReplay, Replies, Surveys, ProactiveReportingConfigOptions, createProactiveReportingConfig, };
|
|
20
20
|
export type { LuciqConfig, Survey, NetworkData, NetworkDataObfuscationHandler, SessionMetadata, ThemeConfig, };
|
|
21
|
+
export { LuciqCaptureScreenLoading } from './components/LuciqCaptureScreenLoading';
|
|
22
|
+
export type { LuciqScreenLoadingProps } from './components/LuciqCaptureScreenLoading';
|
|
21
23
|
export default Luciq;
|
package/dist/index.js
CHANGED
|
@@ -13,4 +13,6 @@ import * as Surveys from './modules/Surveys';
|
|
|
13
13
|
import * as SessionReplay from './modules/SessionReplay';
|
|
14
14
|
export * from './utils/Enums';
|
|
15
15
|
export { Report, CustomSpan, APM, BugReporting, CrashReporting, FeatureRequests, NetworkLogger, SessionReplay, Replies, Surveys, createProactiveReportingConfig, };
|
|
16
|
+
// Screen Loading Component
|
|
17
|
+
export { LuciqCaptureScreenLoading } from './components/LuciqCaptureScreenLoading';
|
|
16
18
|
export default Luciq;
|
package/dist/modules/APM.d.ts
CHANGED
|
@@ -138,3 +138,22 @@ export declare const startCustomSpan: (name: string) => Promise<CustomSpan | nul
|
|
|
138
138
|
* ```
|
|
139
139
|
*/
|
|
140
140
|
export declare const addCompletedCustomSpan: (name: string, startDate: Date, endDate: Date) => Promise<void>;
|
|
141
|
+
/**
|
|
142
|
+
* Enables or disables Screen Loading feature
|
|
143
|
+
* @param isEnabled
|
|
144
|
+
*/
|
|
145
|
+
export declare const setScreenLoadingEnabled: (isEnabled: boolean) => void;
|
|
146
|
+
/**
|
|
147
|
+
* Extends the currently running screen loading trace with a new end timestamp.
|
|
148
|
+
*/
|
|
149
|
+
export declare const endScreenLoading: () => void;
|
|
150
|
+
/**
|
|
151
|
+
* Exclude specific routes from automatic screen loading measurement
|
|
152
|
+
* @param routes Array of route names to exclude
|
|
153
|
+
*/
|
|
154
|
+
export declare function excludeScreenLoadingRoutes(routes: string[]): void;
|
|
155
|
+
/**
|
|
156
|
+
* Include previously excluded routes back into screen loading measurement
|
|
157
|
+
* @param routes Array of route names to include (or empty to clear all exclusions)
|
|
158
|
+
*/
|
|
159
|
+
export declare function includeScreenLoadingRoutes(routes?: string[]): void;
|
package/dist/modules/APM.js
CHANGED
|
@@ -2,6 +2,12 @@ import { Platform } from 'react-native';
|
|
|
2
2
|
import { NativeAPM } from '../native/NativeAPM';
|
|
3
3
|
import { NativeLuciq } from '../native/NativeLuciq';
|
|
4
4
|
import { startCustomSpan as startCustomSpanInternal, addCompletedCustomSpan as addCompletedCustomSpanInternal, } from '../utils/CustomSpansManager';
|
|
5
|
+
import { ScreenLoadingManager } from './apm/ScreenLoadingManager';
|
|
6
|
+
import { Logger } from '../utils/logger';
|
|
7
|
+
// Initialize Screen Loading on module load
|
|
8
|
+
ScreenLoadingManager.initialize().catch((error) => {
|
|
9
|
+
Logger.error('[APM] Failed to initialize Screen Loading:', error);
|
|
10
|
+
});
|
|
5
11
|
/**
|
|
6
12
|
* Enables or disables APM
|
|
7
13
|
* @param isEnabled
|
|
@@ -171,3 +177,35 @@ export const startCustomSpan = async (name) => {
|
|
|
171
177
|
export const addCompletedCustomSpan = async (name, startDate, endDate) => {
|
|
172
178
|
return addCompletedCustomSpanInternal(name, startDate, endDate);
|
|
173
179
|
};
|
|
180
|
+
/**
|
|
181
|
+
* Enables or disables Screen Loading feature
|
|
182
|
+
* @param isEnabled
|
|
183
|
+
*/
|
|
184
|
+
export const setScreenLoadingEnabled = (isEnabled) => {
|
|
185
|
+
try {
|
|
186
|
+
NativeAPM.setScreenLoadingEnabled(isEnabled);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
Logger.error('[APM] Failed to set screen loading enabled:', error);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* Extends the currently running screen loading trace with a new end timestamp.
|
|
194
|
+
*/
|
|
195
|
+
export const endScreenLoading = () => {
|
|
196
|
+
ScreenLoadingManager.endScreenLoading();
|
|
197
|
+
};
|
|
198
|
+
/**
|
|
199
|
+
* Exclude specific routes from automatic screen loading measurement
|
|
200
|
+
* @param routes Array of route names to exclude
|
|
201
|
+
*/
|
|
202
|
+
export function excludeScreenLoadingRoutes(routes) {
|
|
203
|
+
ScreenLoadingManager.excludeRoutes(routes);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Include previously excluded routes back into screen loading measurement
|
|
207
|
+
* @param routes Array of route names to include (or empty to clear all exclusions)
|
|
208
|
+
*/
|
|
209
|
+
export function includeScreenLoadingRoutes(routes) {
|
|
210
|
+
ScreenLoadingManager.includeRoutes(routes);
|
|
211
|
+
}
|
package/dist/modules/Luciq.d.ts
CHANGED
|
@@ -286,7 +286,7 @@ export declare const onStateChange: (state?: NavigationStateV5) => void;
|
|
|
286
286
|
* @param navigationRef a refrence of a navigation container
|
|
287
287
|
*
|
|
288
288
|
*/
|
|
289
|
-
export declare const setNavigationListener: (navigationRef: NavigationContainerRefWithCurrent<ReactNavigation.RootParamList>) =>
|
|
289
|
+
export declare const setNavigationListener: (navigationRef: NavigationContainerRefWithCurrent<ReactNavigation.RootParamList>) => void;
|
|
290
290
|
export declare const reportScreenChange: (screenName: string) => void;
|
|
291
291
|
/**
|
|
292
292
|
* Add feature flags to the next report.
|