@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
|
@@ -32,8 +32,6 @@ 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
|
-
|
|
37
35
|
public final ConcurrentHashMap<String, OnCompleteCallback<NetworkLogSnapshot>> callbackMap = new ConcurrentHashMap<String, OnCompleteCallback<NetworkLogSnapshot>>();
|
|
38
36
|
|
|
39
37
|
public RNLuciqNetworkLoggerModule(ReactApplicationContext reactContext) {
|
|
@@ -59,9 +57,7 @@ public class RNLuciqNetworkLoggerModule extends EventEmitterModule {
|
|
|
59
57
|
}
|
|
60
58
|
|
|
61
59
|
private boolean getFlagValue(String key) {
|
|
62
|
-
|
|
63
|
-
Log.d(NET_TAG, "[getFlagValue] key=" + key + ", value=" + value);
|
|
64
|
-
return value;
|
|
60
|
+
return InternalAPM._isFeatureEnabledCP(key, "");
|
|
65
61
|
}
|
|
66
62
|
|
|
67
63
|
private WritableMap convertFromMapToWritableMap(Map<String, Object> map) {
|
|
@@ -90,18 +86,14 @@ public class RNLuciqNetworkLoggerModule extends EventEmitterModule {
|
|
|
90
86
|
*/
|
|
91
87
|
@ReactMethod
|
|
92
88
|
public void isNativeInterceptionEnabled(Promise promise) {
|
|
93
|
-
Log.d(NET_TAG, "[isNativeInterceptionEnabled] Querying CP_NATIVE_INTERCEPTION_ENABLED flag");
|
|
94
89
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
95
90
|
@Override
|
|
96
91
|
public void run() {
|
|
97
92
|
try {
|
|
98
|
-
|
|
99
|
-
Log.d(NET_TAG, "[isNativeInterceptionEnabled] Result=" + enabled);
|
|
100
|
-
promise.resolve(enabled);
|
|
93
|
+
promise.resolve(getFlagValue(CP_NATIVE_INTERCEPTION_ENABLED));
|
|
101
94
|
} catch (Exception e) {
|
|
102
|
-
Log.e(NET_TAG, "[isNativeInterceptionEnabled] Error — falling back to false (JS interceptor)", e);
|
|
103
95
|
e.printStackTrace();
|
|
104
|
-
promise.resolve(false);
|
|
96
|
+
promise.resolve(false); // Will rollback to JS interceptor
|
|
105
97
|
}
|
|
106
98
|
|
|
107
99
|
}
|
|
@@ -115,18 +107,14 @@ public class RNLuciqNetworkLoggerModule extends EventEmitterModule {
|
|
|
115
107
|
*/
|
|
116
108
|
@ReactMethod
|
|
117
109
|
public void hasAPMNetworkPlugin(Promise promise) {
|
|
118
|
-
Log.d(NET_TAG, "[hasAPMNetworkPlugin] Querying APM_NETWORK_PLUGIN_INSTALLED flag");
|
|
119
110
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
120
111
|
@Override
|
|
121
112
|
public void run() {
|
|
122
113
|
try {
|
|
123
|
-
|
|
124
|
-
Log.d(NET_TAG, "[hasAPMNetworkPlugin] Result=" + hasPlugin);
|
|
125
|
-
promise.resolve(hasPlugin);
|
|
114
|
+
promise.resolve(getFlagValue(APM_NETWORK_PLUGIN_INSTALLED));
|
|
126
115
|
} catch (Exception e) {
|
|
127
|
-
Log.e(NET_TAG, "[hasAPMNetworkPlugin] Error — falling back to false", e);
|
|
128
116
|
e.printStackTrace();
|
|
129
|
-
promise.resolve(false);
|
|
117
|
+
promise.resolve(false); // Will rollback to JS interceptor
|
|
130
118
|
}
|
|
131
119
|
|
|
132
120
|
}
|
|
@@ -136,14 +124,12 @@ public class RNLuciqNetworkLoggerModule extends EventEmitterModule {
|
|
|
136
124
|
|
|
137
125
|
@ReactMethod
|
|
138
126
|
public void registerNetworkLogsListener() {
|
|
139
|
-
Log.d(NET_TAG, "[registerNetworkLogsListener] Registering network log sanitizer");
|
|
140
127
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
141
128
|
@Override
|
|
142
129
|
public void run() {
|
|
143
130
|
InternalAPM._registerNetworkLogSanitizer((networkLogSnapshot, onCompleteCallback) -> {
|
|
144
131
|
final String id = String.valueOf(onCompleteCallback.hashCode());
|
|
145
132
|
callbackMap.put(id, onCompleteCallback);
|
|
146
|
-
Log.d(NET_TAG, "[NetworkLogSanitizer] Received snapshot — id=" + id + ", url=" + networkLogSnapshot.getUrl() + ", responseCode=" + networkLogSnapshot.getResponseCode() + ", callbackMapSize=" + callbackMap.size());
|
|
147
133
|
|
|
148
134
|
WritableMap networkSnapshotParams = Arguments.createMap();
|
|
149
135
|
networkSnapshotParams.putString("id", id);
|
|
@@ -161,7 +147,6 @@ public class RNLuciqNetworkLoggerModule extends EventEmitterModule {
|
|
|
161
147
|
}
|
|
162
148
|
|
|
163
149
|
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());
|
|
165
150
|
});
|
|
166
151
|
}
|
|
167
152
|
});
|
|
@@ -169,12 +154,10 @@ public class RNLuciqNetworkLoggerModule extends EventEmitterModule {
|
|
|
169
154
|
|
|
170
155
|
@ReactMethod
|
|
171
156
|
public void resetNetworkLogsListener() {
|
|
172
|
-
Log.d(NET_TAG, "[resetNetworkLogsListener] Clearing network log sanitizer, callbackMapSize=" + callbackMap.size());
|
|
173
157
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
174
158
|
@Override
|
|
175
159
|
public void run() {
|
|
176
160
|
InternalAPM._registerNetworkLogSanitizer(null);
|
|
177
|
-
Log.d(NET_TAG, "[resetNetworkLogsListener] Sanitizer cleared");
|
|
178
161
|
}
|
|
179
162
|
});
|
|
180
163
|
}
|
|
@@ -189,28 +172,23 @@ public class RNLuciqNetworkLoggerModule extends EventEmitterModule {
|
|
|
189
172
|
ReadableMap requestHeaders,
|
|
190
173
|
ReadableMap responseHeaders
|
|
191
174
|
) {
|
|
192
|
-
Log.d(NET_TAG, "[updateNetworkLogSnapshot] callbackID=" + callbackID + ", url=" + url + ", responseCode=" + responseCode + ", callbackMapSize=" + callbackMap.size());
|
|
193
175
|
try {
|
|
176
|
+
// Convert ReadableMap to a Java Map for easier handling
|
|
194
177
|
Map<String, Object> requestHeadersMap = convertReadableMapToMap(requestHeaders);
|
|
195
178
|
Map<String, Object> responseHeadersMap = convertReadableMapToMap(responseHeaders);
|
|
196
179
|
|
|
197
180
|
NetworkLogSnapshot modifiedSnapshot = null;
|
|
198
181
|
if (!url.isEmpty()) {
|
|
199
182
|
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)");
|
|
202
183
|
}
|
|
203
184
|
|
|
204
185
|
final OnCompleteCallback<NetworkLogSnapshot> callback = callbackMap.get(callbackID);
|
|
205
186
|
if (callback != null) {
|
|
206
187
|
callback.onComplete(modifiedSnapshot);
|
|
207
188
|
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());
|
|
211
189
|
}
|
|
212
190
|
} catch (Exception e) {
|
|
213
|
-
|
|
191
|
+
// Reject the promise to indicate an error occurred
|
|
214
192
|
Log.e("IB-CP-Bridge", "LuciqNetworkLogger.updateNetworkLogSnapshot failed to parse the network snapshot object.");
|
|
215
193
|
}
|
|
216
194
|
}
|
|
@@ -86,7 +86,6 @@ 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";
|
|
90
89
|
|
|
91
90
|
private LuciqCustomTextPlaceHolder placeHolders;
|
|
92
91
|
private static Report currentReport;
|
|
@@ -164,7 +163,6 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
164
163
|
|
|
165
164
|
|
|
166
165
|
) {
|
|
167
|
-
Log.d(NET_TAG, "[init] Called — logLevel=" + logLevel + ", useNativeNetworkInterception=" + useNativeNetworkInterception + ", codePushVersion=" + codePushVersion + ", appVariant=" + appVariant);
|
|
168
166
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
169
167
|
@Override
|
|
170
168
|
public void run() {
|
|
@@ -206,7 +204,6 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
206
204
|
}
|
|
207
205
|
|
|
208
206
|
builder.build();
|
|
209
|
-
Log.d(NET_TAG, "[init] SDK build complete");
|
|
210
207
|
}
|
|
211
208
|
});
|
|
212
209
|
}
|
|
@@ -972,7 +969,6 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
972
969
|
final String requestHeaders,
|
|
973
970
|
final String responseHeaders,
|
|
974
971
|
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));
|
|
976
972
|
try {
|
|
977
973
|
final String date = String.valueOf(System.currentTimeMillis());
|
|
978
974
|
|
|
@@ -989,14 +985,11 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
989
985
|
networkLog.setRequestHeaders(requestHeaders);
|
|
990
986
|
networkLog.setResponseHeaders(responseHeaders);
|
|
991
987
|
} catch (OutOfMemoryError | Exception exception) {
|
|
992
|
-
Log.e(NET_TAG, "[networkLogAndroid-Core] OOM/Error setting log contents: " + exception.getMessage() + " for " + method + " " + url);
|
|
993
988
|
Log.d(TAG, "Error: " + exception.getMessage() + "while trying to set network log contents (request body, response body, request headers, and response headers).");
|
|
994
989
|
}
|
|
995
990
|
|
|
996
991
|
networkLog.insert();
|
|
997
|
-
Log.d(NET_TAG, "[networkLogAndroid-Core] Successfully inserted NetworkLog: " + method + " " + url);
|
|
998
992
|
} catch (OutOfMemoryError | Exception exception) {
|
|
999
|
-
Log.e(NET_TAG, "[networkLogAndroid-Core] OOM/Error inserting network log: " + exception.getMessage() + " for " + method + " " + url);
|
|
1000
993
|
Log.d(TAG, "Error: " + exception.getMessage() + "while trying to insert a network log");
|
|
1001
994
|
}
|
|
1002
995
|
}
|
|
@@ -1085,16 +1078,18 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1085
1078
|
* Reports that the screen has been changed (Repro Steps) the screen sent to this method will be the 'current view' on the dashboard
|
|
1086
1079
|
*
|
|
1087
1080
|
* @param screenName string containing the screen name
|
|
1081
|
+
* @param spanId the span ID for screen loading tracking (nullable)
|
|
1088
1082
|
*/
|
|
1089
1083
|
@ReactMethod
|
|
1090
|
-
public void reportScreenChange(final String screenName) {
|
|
1084
|
+
public void reportScreenChange(final String screenName, @Nullable final String spanId) {
|
|
1091
1085
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
1092
1086
|
@Override
|
|
1093
1087
|
public void run() {
|
|
1094
1088
|
try {
|
|
1095
|
-
|
|
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);
|
|
1096
1091
|
if (method != null) {
|
|
1097
|
-
method.invoke(null, null, screenName);
|
|
1092
|
+
method.invoke(null, null, screenName , uiTraceId);
|
|
1098
1093
|
}
|
|
1099
1094
|
} catch (Exception e) {
|
|
1100
1095
|
e.printStackTrace();
|
|
@@ -1177,7 +1172,7 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1177
1172
|
*/
|
|
1178
1173
|
@ReactMethod
|
|
1179
1174
|
public void registerFeatureFlagsChangeListener() {
|
|
1180
|
-
|
|
1175
|
+
|
|
1181
1176
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
1182
1177
|
@Override
|
|
1183
1178
|
public void run() {
|
|
@@ -1185,7 +1180,6 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1185
1180
|
InternalCore.INSTANCE._setFeaturesStateListener(new FeaturesStateListener() {
|
|
1186
1181
|
@Override
|
|
1187
1182
|
public void invoke(@NonNull CoreFeaturesState featuresState) {
|
|
1188
|
-
Log.d(NET_TAG, "[FeatureFlagsListener] Received update — W3CTraceID=" + featuresState.isW3CExternalTraceIdEnabled() + ", generatedHeader=" + featuresState.isAttachingGeneratedHeaderEnabled() + ", caughtHeader=" + featuresState.isAttachingCapturedHeaderEnabled() + ", networkBodyLimit=" + featuresState.getNetworkLogCharLimit());
|
|
1189
1183
|
WritableMap params = Arguments.createMap();
|
|
1190
1184
|
params.putBoolean("isW3ExternalTraceIDEnabled", featuresState.isW3CExternalTraceIdEnabled());
|
|
1191
1185
|
params.putBoolean("isW3ExternalGeneratedHeaderEnabled", featuresState.isAttachingGeneratedHeaderEnabled());
|
|
@@ -1193,11 +1187,9 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1193
1187
|
params.putInt("networkBodyLimit",featuresState.getNetworkLogCharLimit());
|
|
1194
1188
|
|
|
1195
1189
|
sendEvent(Constants.LCQ_ON_FEATURE_FLAGS_UPDATE_RECEIVED_CALLBACK, params);
|
|
1196
|
-
Log.d(NET_TAG, "[FeatureFlagsListener] Sent event to JS: " + Constants.LCQ_ON_FEATURE_FLAGS_UPDATE_RECEIVED_CALLBACK);
|
|
1197
1190
|
}
|
|
1198
1191
|
});
|
|
1199
1192
|
} catch (Exception e) {
|
|
1200
|
-
Log.e(NET_TAG, "[registerFeatureFlagsChangeListener] Failed to register listener", e);
|
|
1201
1193
|
e.printStackTrace();
|
|
1202
1194
|
}
|
|
1203
1195
|
|
|
@@ -1212,16 +1204,13 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1212
1204
|
*/
|
|
1213
1205
|
@ReactMethod
|
|
1214
1206
|
public void isW3ExternalTraceIDEnabled(Promise promise) {
|
|
1215
|
-
|
|
1207
|
+
|
|
1216
1208
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
1217
1209
|
@Override
|
|
1218
1210
|
public void run() {
|
|
1219
1211
|
try {
|
|
1220
|
-
|
|
1221
|
-
Log.d(NET_TAG, "[isW3ExternalTraceIDEnabled] Result=" + enabled);
|
|
1222
|
-
promise.resolve(enabled);
|
|
1212
|
+
promise.resolve(InternalCore.INSTANCE._isFeatureEnabled(CoreFeature.W3C_EXTERNAL_TRACE_ID));
|
|
1223
1213
|
} catch (Exception e) {
|
|
1224
|
-
Log.e(NET_TAG, "[isW3ExternalTraceIDEnabled] Error querying flag", e);
|
|
1225
1214
|
e.printStackTrace();
|
|
1226
1215
|
promise.resolve(false);
|
|
1227
1216
|
}
|
|
@@ -1237,16 +1226,13 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1237
1226
|
*/
|
|
1238
1227
|
@ReactMethod
|
|
1239
1228
|
public void isW3ExternalGeneratedHeaderEnabled(Promise promise) {
|
|
1240
|
-
|
|
1229
|
+
|
|
1241
1230
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
1242
1231
|
@Override
|
|
1243
1232
|
public void run() {
|
|
1244
1233
|
try {
|
|
1245
|
-
|
|
1246
|
-
Log.d(NET_TAG, "[isW3ExternalGeneratedHeaderEnabled] Result=" + enabled);
|
|
1247
|
-
promise.resolve(enabled);
|
|
1234
|
+
promise.resolve(InternalCore.INSTANCE._isFeatureEnabled(CoreFeature.W3C_ATTACHING_GENERATED_HEADER));
|
|
1248
1235
|
} catch (Exception e) {
|
|
1249
|
-
Log.e(NET_TAG, "[isW3ExternalGeneratedHeaderEnabled] Error querying flag", e);
|
|
1250
1236
|
e.printStackTrace();
|
|
1251
1237
|
promise.resolve(false);
|
|
1252
1238
|
}
|
|
@@ -1261,16 +1247,13 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1261
1247
|
*/
|
|
1262
1248
|
@ReactMethod
|
|
1263
1249
|
public void isW3CaughtHeaderEnabled(Promise promise) {
|
|
1264
|
-
|
|
1250
|
+
|
|
1265
1251
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
1266
1252
|
@Override
|
|
1267
1253
|
public void run() {
|
|
1268
1254
|
try {
|
|
1269
|
-
|
|
1270
|
-
Log.d(NET_TAG, "[isW3CaughtHeaderEnabled] Result=" + enabled);
|
|
1271
|
-
promise.resolve(enabled);
|
|
1255
|
+
promise.resolve(InternalCore.INSTANCE._isFeatureEnabled(CoreFeature.W3C_ATTACHING_CAPTURED_HEADER));
|
|
1272
1256
|
} catch (Exception e) {
|
|
1273
|
-
Log.e(NET_TAG, "[isW3CaughtHeaderEnabled] Error querying flag", e);
|
|
1274
1257
|
e.printStackTrace();
|
|
1275
1258
|
promise.resolve(false);
|
|
1276
1259
|
}
|
|
@@ -1364,16 +1347,13 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1364
1347
|
*/
|
|
1365
1348
|
@ReactMethod
|
|
1366
1349
|
public void getNetworkBodyMaxSize(Promise promise) {
|
|
1367
|
-
|
|
1350
|
+
|
|
1368
1351
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
1369
1352
|
@Override
|
|
1370
1353
|
public void run() {
|
|
1371
1354
|
try {
|
|
1372
|
-
|
|
1373
|
-
Log.d(NET_TAG, "[getNetworkBodyMaxSize] Result=" + limit);
|
|
1374
|
-
promise.resolve(limit);
|
|
1355
|
+
promise.resolve(InternalCore.INSTANCE.get_networkLogCharLimit());
|
|
1375
1356
|
} catch (Exception e) {
|
|
1376
|
-
Log.e(NET_TAG, "[getNetworkBodyMaxSize] Error querying limit", e);
|
|
1377
1357
|
e.printStackTrace();
|
|
1378
1358
|
promise.resolve(false);
|
|
1379
1359
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
package ai.luciq.reactlibrary.utils;
|
|
2
2
|
|
|
3
|
-
import android.util.Log;
|
|
4
|
-
|
|
5
3
|
import androidx.annotation.Nullable;
|
|
6
4
|
import androidx.annotation.VisibleForTesting;
|
|
7
5
|
|
|
@@ -12,7 +10,6 @@ import com.facebook.react.bridge.WritableMap;
|
|
|
12
10
|
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
13
11
|
|
|
14
12
|
public abstract class EventEmitterModule extends ReactContextBaseJavaModule {
|
|
15
|
-
private static final String NET_TAG = "LCQ-RN-NET";
|
|
16
13
|
private int listenerCount = 0;
|
|
17
14
|
|
|
18
15
|
public EventEmitterModule(ReactApplicationContext context) {
|
|
@@ -25,18 +22,14 @@ public abstract class EventEmitterModule extends ReactContextBaseJavaModule {
|
|
|
25
22
|
getReactApplicationContext()
|
|
26
23
|
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
27
24
|
.emit(event, params);
|
|
28
|
-
} else {
|
|
29
|
-
Log.w(NET_TAG, "[EventEmitter] Event DROPPED (no JS listeners): event=" + event + ", module=" + getName() + ", listenerCount=0");
|
|
30
25
|
}
|
|
31
26
|
}
|
|
32
27
|
|
|
33
28
|
protected void addListener(String ignoredEvent) {
|
|
34
29
|
listenerCount++;
|
|
35
|
-
Log.d(NET_TAG, "[EventEmitter] addListener — module=" + getName() + ", event=" + ignoredEvent + ", listenerCount=" + listenerCount);
|
|
36
30
|
}
|
|
37
31
|
|
|
38
32
|
protected void removeListeners(Integer count) {
|
|
39
33
|
listenerCount -= count;
|
|
40
|
-
Log.d(NET_TAG, "[EventEmitter] removeListeners — module=" + getName() + ", removed=" + count + ", listenerCount=" + listenerCount);
|
|
41
34
|
}
|
|
42
35
|
}
|
|
@@ -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.
|