@luciq/react-native 19.2.1 → 19.3.0-40271-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/CHANGELOG.md +30 -0
- package/README.md +87 -0
- package/RNLuciq.podspec +1 -1
- package/android/native.gradle +1 -1
- package/android/src/main/java/ai/luciq/reactlibrary/RNLuciqAPMModule.java +211 -117
- package/android/src/main/java/ai/luciq/reactlibrary/RNLuciqNetworkLoggerModule.java +29 -7
- package/android/src/main/java/ai/luciq/reactlibrary/RNLuciqReactnativeModule.java +51 -9
- package/android/src/main/java/ai/luciq/reactlibrary/utils/EventEmitterModule.java +7 -0
- package/dist/constants/Strings.d.ts +9 -0
- package/dist/constants/Strings.js +12 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/models/CustomSpan.d.ts +47 -0
- package/dist/models/CustomSpan.js +82 -0
- package/dist/modules/APM.d.ts +58 -0
- package/dist/modules/APM.js +62 -0
- package/dist/modules/Luciq.js +2 -1
- package/dist/modules/NetworkLogger.d.ts +0 -5
- package/dist/modules/NetworkLogger.js +9 -1
- package/dist/native/NativeAPM.d.ts +3 -0
- package/dist/native/NativeLuciq.d.ts +1 -0
- package/dist/utils/CustomSpansManager.d.ts +38 -0
- package/dist/utils/CustomSpansManager.js +173 -0
- package/dist/utils/FeatureFlags.d.ts +6 -0
- package/dist/utils/FeatureFlags.js +35 -0
- package/dist/utils/LuciqUtils.js +6 -0
- package/dist/utils/XhrNetworkInterceptor.js +85 -53
- package/ios/RNLuciq/LuciqAPMBridge.h +13 -0
- package/ios/RNLuciq/LuciqAPMBridge.m +55 -0
- package/ios/RNLuciq/LuciqReactBridge.m +12 -0
- package/ios/RNLuciq/Util/LCQAPM+PrivateAPIs.h +1 -0
- package/ios/native.rb +1 -1
- package/package.json +1 -2
- package/plugin/build/index.js +9 -2
- package/plugin/src/withLuciqIOS.ts +9 -2
- package/scripts/releases/changelog_to_slack_formatter.sh +9 -0
- package/scripts/releases/get_job_approver.sh +60 -0
- package/scripts/releases/get_release_notes.sh +22 -0
- package/scripts/releases/get_sdk_version.sh +5 -0
- package/scripts/releases/get_slack_id_from_username.sh +24 -0
- package/src/constants/Strings.ts +24 -0
- package/src/index.ts +2 -0
- package/src/models/CustomSpan.ts +102 -0
- package/src/modules/APM.ts +72 -0
- package/src/modules/Luciq.ts +3 -1
- package/src/modules/NetworkLogger.ts +26 -1
- package/src/native/NativeAPM.ts +7 -0
- package/src/native/NativeLuciq.ts +1 -0
- package/src/utils/CustomSpansManager.ts +202 -0
- package/src/utils/FeatureFlags.ts +44 -0
- package/src/utils/LuciqUtils.ts +15 -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
|
}
|
|
@@ -222,6 +225,26 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
222
225
|
});
|
|
223
226
|
}
|
|
224
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Checks if the SDK is built or not.
|
|
230
|
+
*
|
|
231
|
+
* @param promise Promise that resolves with boolean indicating if enabled
|
|
232
|
+
*/
|
|
233
|
+
@ReactMethod
|
|
234
|
+
public void isBuilt(final Promise promise) {
|
|
235
|
+
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
236
|
+
@Override
|
|
237
|
+
public void run() {
|
|
238
|
+
try {
|
|
239
|
+
promise.resolve(Luciq.isBuilt());
|
|
240
|
+
} catch (Exception e) {
|
|
241
|
+
e.printStackTrace();
|
|
242
|
+
promise.resolve(false);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
225
248
|
@ReactMethod
|
|
226
249
|
public void setOverAirVersion(@Nullable final ReadableMap overAirVersion) {
|
|
227
250
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
@@ -949,6 +972,7 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
949
972
|
final String requestHeaders,
|
|
950
973
|
final String responseHeaders,
|
|
951
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));
|
|
952
976
|
try {
|
|
953
977
|
final String date = String.valueOf(System.currentTimeMillis());
|
|
954
978
|
|
|
@@ -965,11 +989,14 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
965
989
|
networkLog.setRequestHeaders(requestHeaders);
|
|
966
990
|
networkLog.setResponseHeaders(responseHeaders);
|
|
967
991
|
} catch (OutOfMemoryError | Exception exception) {
|
|
992
|
+
Log.e(NET_TAG, "[networkLogAndroid-Core] OOM/Error setting log contents: " + exception.getMessage() + " for " + method + " " + url);
|
|
968
993
|
Log.d(TAG, "Error: " + exception.getMessage() + "while trying to set network log contents (request body, response body, request headers, and response headers).");
|
|
969
994
|
}
|
|
970
995
|
|
|
971
996
|
networkLog.insert();
|
|
997
|
+
Log.d(NET_TAG, "[networkLogAndroid-Core] Successfully inserted NetworkLog: " + method + " " + url);
|
|
972
998
|
} catch (OutOfMemoryError | Exception exception) {
|
|
999
|
+
Log.e(NET_TAG, "[networkLogAndroid-Core] OOM/Error inserting network log: " + exception.getMessage() + " for " + method + " " + url);
|
|
973
1000
|
Log.d(TAG, "Error: " + exception.getMessage() + "while trying to insert a network log");
|
|
974
1001
|
}
|
|
975
1002
|
}
|
|
@@ -1150,7 +1177,7 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1150
1177
|
*/
|
|
1151
1178
|
@ReactMethod
|
|
1152
1179
|
public void registerFeatureFlagsChangeListener() {
|
|
1153
|
-
|
|
1180
|
+
Log.d(NET_TAG, "[registerFeatureFlagsChangeListener] Registering native feature flags listener");
|
|
1154
1181
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
1155
1182
|
@Override
|
|
1156
1183
|
public void run() {
|
|
@@ -1158,6 +1185,7 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1158
1185
|
InternalCore.INSTANCE._setFeaturesStateListener(new FeaturesStateListener() {
|
|
1159
1186
|
@Override
|
|
1160
1187
|
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());
|
|
1161
1189
|
WritableMap params = Arguments.createMap();
|
|
1162
1190
|
params.putBoolean("isW3ExternalTraceIDEnabled", featuresState.isW3CExternalTraceIdEnabled());
|
|
1163
1191
|
params.putBoolean("isW3ExternalGeneratedHeaderEnabled", featuresState.isAttachingGeneratedHeaderEnabled());
|
|
@@ -1165,9 +1193,11 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1165
1193
|
params.putInt("networkBodyLimit",featuresState.getNetworkLogCharLimit());
|
|
1166
1194
|
|
|
1167
1195
|
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);
|
|
1168
1197
|
}
|
|
1169
1198
|
});
|
|
1170
1199
|
} catch (Exception e) {
|
|
1200
|
+
Log.e(NET_TAG, "[registerFeatureFlagsChangeListener] Failed to register listener", e);
|
|
1171
1201
|
e.printStackTrace();
|
|
1172
1202
|
}
|
|
1173
1203
|
|
|
@@ -1182,13 +1212,16 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1182
1212
|
*/
|
|
1183
1213
|
@ReactMethod
|
|
1184
1214
|
public void isW3ExternalTraceIDEnabled(Promise promise) {
|
|
1185
|
-
|
|
1215
|
+
Log.d(NET_TAG, "[isW3ExternalTraceIDEnabled] Querying native flag");
|
|
1186
1216
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
1187
1217
|
@Override
|
|
1188
1218
|
public void run() {
|
|
1189
1219
|
try {
|
|
1190
|
-
|
|
1220
|
+
boolean enabled = InternalCore.INSTANCE._isFeatureEnabled(CoreFeature.W3C_EXTERNAL_TRACE_ID);
|
|
1221
|
+
Log.d(NET_TAG, "[isW3ExternalTraceIDEnabled] Result=" + enabled);
|
|
1222
|
+
promise.resolve(enabled);
|
|
1191
1223
|
} catch (Exception e) {
|
|
1224
|
+
Log.e(NET_TAG, "[isW3ExternalTraceIDEnabled] Error querying flag", e);
|
|
1192
1225
|
e.printStackTrace();
|
|
1193
1226
|
promise.resolve(false);
|
|
1194
1227
|
}
|
|
@@ -1204,13 +1237,16 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1204
1237
|
*/
|
|
1205
1238
|
@ReactMethod
|
|
1206
1239
|
public void isW3ExternalGeneratedHeaderEnabled(Promise promise) {
|
|
1207
|
-
|
|
1240
|
+
Log.d(NET_TAG, "[isW3ExternalGeneratedHeaderEnabled] Querying native flag");
|
|
1208
1241
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
1209
1242
|
@Override
|
|
1210
1243
|
public void run() {
|
|
1211
1244
|
try {
|
|
1212
|
-
|
|
1245
|
+
boolean enabled = InternalCore.INSTANCE._isFeatureEnabled(CoreFeature.W3C_ATTACHING_GENERATED_HEADER);
|
|
1246
|
+
Log.d(NET_TAG, "[isW3ExternalGeneratedHeaderEnabled] Result=" + enabled);
|
|
1247
|
+
promise.resolve(enabled);
|
|
1213
1248
|
} catch (Exception e) {
|
|
1249
|
+
Log.e(NET_TAG, "[isW3ExternalGeneratedHeaderEnabled] Error querying flag", e);
|
|
1214
1250
|
e.printStackTrace();
|
|
1215
1251
|
promise.resolve(false);
|
|
1216
1252
|
}
|
|
@@ -1225,13 +1261,16 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1225
1261
|
*/
|
|
1226
1262
|
@ReactMethod
|
|
1227
1263
|
public void isW3CaughtHeaderEnabled(Promise promise) {
|
|
1228
|
-
|
|
1264
|
+
Log.d(NET_TAG, "[isW3CaughtHeaderEnabled] Querying native flag");
|
|
1229
1265
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
1230
1266
|
@Override
|
|
1231
1267
|
public void run() {
|
|
1232
1268
|
try {
|
|
1233
|
-
|
|
1269
|
+
boolean enabled = InternalCore.INSTANCE._isFeatureEnabled(CoreFeature.W3C_ATTACHING_CAPTURED_HEADER);
|
|
1270
|
+
Log.d(NET_TAG, "[isW3CaughtHeaderEnabled] Result=" + enabled);
|
|
1271
|
+
promise.resolve(enabled);
|
|
1234
1272
|
} catch (Exception e) {
|
|
1273
|
+
Log.e(NET_TAG, "[isW3CaughtHeaderEnabled] Error querying flag", e);
|
|
1235
1274
|
e.printStackTrace();
|
|
1236
1275
|
promise.resolve(false);
|
|
1237
1276
|
}
|
|
@@ -1325,13 +1364,16 @@ public class RNLuciqReactnativeModule extends EventEmitterModule {
|
|
|
1325
1364
|
*/
|
|
1326
1365
|
@ReactMethod
|
|
1327
1366
|
public void getNetworkBodyMaxSize(Promise promise) {
|
|
1328
|
-
|
|
1367
|
+
Log.d(NET_TAG, "[getNetworkBodyMaxSize] Querying network body size limit");
|
|
1329
1368
|
MainThreadHandler.runOnMainThread(new Runnable() {
|
|
1330
1369
|
@Override
|
|
1331
1370
|
public void run() {
|
|
1332
1371
|
try {
|
|
1333
|
-
|
|
1372
|
+
Object limit = InternalCore.INSTANCE.get_networkLogCharLimit();
|
|
1373
|
+
Log.d(NET_TAG, "[getNetworkBodyMaxSize] Result=" + limit);
|
|
1374
|
+
promise.resolve(limit);
|
|
1334
1375
|
} catch (Exception e) {
|
|
1376
|
+
Log.e(NET_TAG, "[getNetworkBodyMaxSize] Error querying limit", e);
|
|
1335
1377
|
e.printStackTrace();
|
|
1336
1378
|
promise.resolve(false);
|
|
1337
1379
|
}
|
|
@@ -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,9 @@
|
|
|
1
|
+
export declare class LuciqStrings {
|
|
2
|
+
static readonly customSpanAPMDisabledMessage: string;
|
|
3
|
+
static readonly customSpanDisabled: string;
|
|
4
|
+
static readonly customSpanSDKNotInitializedMessage: string;
|
|
5
|
+
static readonly customSpanNameEmpty: string;
|
|
6
|
+
static readonly customSpanEndTimeBeforeStartTime: string;
|
|
7
|
+
static readonly customSpanNameTruncated: string;
|
|
8
|
+
static readonly customSpanLimitReached: string;
|
|
9
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export class LuciqStrings {
|
|
2
|
+
static customSpanAPMDisabledMessage = 'APM is disabled, custom span not created. Please enable APM by following the instructions at this link:\n' +
|
|
3
|
+
'https://docs.luciq.ai/product-guides-and-integrations/product-guides/application-performance-monitoring';
|
|
4
|
+
static customSpanDisabled = 'Custom span is disabled, custom span not created. Please enable Custom Span by following the instructions at this link:\n' +
|
|
5
|
+
'https://docs.luciq.ai/product-guides-and-integrations/product-guides/application-performance-monitoring';
|
|
6
|
+
static customSpanSDKNotInitializedMessage = 'Luciq API was called before the SDK is built. To build it, first by following the instructions at this link:\n' +
|
|
7
|
+
'https://docs.luciq.ai/product-guides-and-integrations/product-guides/application-performance-monitoring';
|
|
8
|
+
static customSpanNameEmpty = 'Custom span name cannot be empty. Please provide a valid name for the custom span.';
|
|
9
|
+
static customSpanEndTimeBeforeStartTime = 'Custom span end time must be after start time. Please provide a valid start and end time for the custom span.';
|
|
10
|
+
static customSpanNameTruncated = 'Custom span name truncated to 150 characters';
|
|
11
|
+
static customSpanLimitReached = 'Maximum number of concurrent custom spans (100) reached. Please end some spans before starting new ones.';
|
|
12
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { LuciqConfig } from './models/LuciqConfig';
|
|
2
2
|
import Report from './models/Report';
|
|
3
3
|
import type { ThemeConfig } from './models/ThemeConfig';
|
|
4
|
+
import { CustomSpan } from './models/CustomSpan';
|
|
4
5
|
import * as APM from './modules/APM';
|
|
5
6
|
import * as BugReporting from './modules/BugReporting';
|
|
6
7
|
import * as CrashReporting from './modules/CrashReporting';
|
|
@@ -15,6 +16,6 @@ import * as Surveys from './modules/Surveys';
|
|
|
15
16
|
import * as SessionReplay from './modules/SessionReplay';
|
|
16
17
|
import type { SessionMetadata } from './models/SessionMetadata';
|
|
17
18
|
export * from './utils/Enums';
|
|
18
|
-
export { Report, APM, BugReporting, CrashReporting, FeatureRequests, NetworkLogger, SessionReplay, Replies, Surveys, ProactiveReportingConfigOptions, createProactiveReportingConfig, };
|
|
19
|
+
export { Report, CustomSpan, APM, BugReporting, CrashReporting, FeatureRequests, NetworkLogger, SessionReplay, Replies, Surveys, ProactiveReportingConfigOptions, createProactiveReportingConfig, };
|
|
19
20
|
export type { LuciqConfig, Survey, NetworkData, NetworkDataObfuscationHandler, SessionMetadata, ThemeConfig, };
|
|
20
21
|
export default Luciq;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Report from './models/Report';
|
|
2
|
+
import { CustomSpan } from './models/CustomSpan';
|
|
2
3
|
// Modules
|
|
3
4
|
import * as APM from './modules/APM';
|
|
4
5
|
import * as BugReporting from './modules/BugReporting';
|
|
@@ -11,5 +12,5 @@ import * as Replies from './modules/Replies';
|
|
|
11
12
|
import * as Surveys from './modules/Surveys';
|
|
12
13
|
import * as SessionReplay from './modules/SessionReplay';
|
|
13
14
|
export * from './utils/Enums';
|
|
14
|
-
export { Report, APM, BugReporting, CrashReporting, FeatureRequests, NetworkLogger, SessionReplay, Replies, Surveys, createProactiveReportingConfig, };
|
|
15
|
+
export { Report, CustomSpan, APM, BugReporting, CrashReporting, FeatureRequests, NetworkLogger, SessionReplay, Replies, Surveys, createProactiveReportingConfig, };
|
|
15
16
|
export default Luciq;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Callback to unregister a span from tracking
|
|
3
|
+
*/
|
|
4
|
+
type UnregisterCallback = (span: CustomSpan) => void;
|
|
5
|
+
/**
|
|
6
|
+
* Callback to sync span data to native SDK
|
|
7
|
+
*/
|
|
8
|
+
type SyncCallback = (name: string, startTimestamp: number, endTimestamp: number) => Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Represents a custom span for performance tracking.
|
|
11
|
+
* A span measures the duration of an operation and reports it to the native SDK.
|
|
12
|
+
*/
|
|
13
|
+
export declare class CustomSpan {
|
|
14
|
+
private name;
|
|
15
|
+
private startTime;
|
|
16
|
+
private startMonotonic;
|
|
17
|
+
private endTime?;
|
|
18
|
+
private duration?;
|
|
19
|
+
private hasEnded;
|
|
20
|
+
private endPromise?;
|
|
21
|
+
private unregisterCallback;
|
|
22
|
+
private syncCallback;
|
|
23
|
+
/**
|
|
24
|
+
* Creates a new custom span. The span starts immediately upon creation.
|
|
25
|
+
* @internal - Use APM.startCustomSpan() instead
|
|
26
|
+
*/
|
|
27
|
+
constructor(name: string, unregisterCallback: UnregisterCallback, syncCallback: SyncCallback);
|
|
28
|
+
/**
|
|
29
|
+
* Ends this custom span and reports it to the native SDK.
|
|
30
|
+
* This method is idempotent - calling it multiple times is safe.
|
|
31
|
+
* Subsequent calls will wait for the first call to complete.
|
|
32
|
+
*/
|
|
33
|
+
end(): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Get the span name
|
|
36
|
+
*/
|
|
37
|
+
getName(): string;
|
|
38
|
+
/**
|
|
39
|
+
* Check if the span has ended
|
|
40
|
+
*/
|
|
41
|
+
isEnded(): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Get the span duration in milliseconds (only available after end())
|
|
44
|
+
*/
|
|
45
|
+
getDuration(): number | undefined;
|
|
46
|
+
}
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a custom span for performance tracking.
|
|
3
|
+
* A span measures the duration of an operation and reports it to the native SDK.
|
|
4
|
+
*/
|
|
5
|
+
export class CustomSpan {
|
|
6
|
+
name;
|
|
7
|
+
startTime; // Date.now() in milliseconds
|
|
8
|
+
startMonotonic; // performance.now() in milliseconds
|
|
9
|
+
endTime;
|
|
10
|
+
duration;
|
|
11
|
+
hasEnded = false;
|
|
12
|
+
endPromise;
|
|
13
|
+
unregisterCallback;
|
|
14
|
+
syncCallback;
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new custom span. The span starts immediately upon creation.
|
|
17
|
+
* @internal - Use APM.startCustomSpan() instead
|
|
18
|
+
*/
|
|
19
|
+
constructor(name, unregisterCallback, syncCallback) {
|
|
20
|
+
this.name = name;
|
|
21
|
+
this.startTime = Date.now();
|
|
22
|
+
this.startMonotonic = performance.now();
|
|
23
|
+
this.unregisterCallback = unregisterCallback;
|
|
24
|
+
this.syncCallback = syncCallback;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Ends this custom span and reports it to the native SDK.
|
|
28
|
+
* This method is idempotent - calling it multiple times is safe.
|
|
29
|
+
* Subsequent calls will wait for the first call to complete.
|
|
30
|
+
*/
|
|
31
|
+
async end() {
|
|
32
|
+
// Thread-safe check using Promise-based locking
|
|
33
|
+
if (this.hasEnded) {
|
|
34
|
+
if (this.endPromise) {
|
|
35
|
+
await this.endPromise;
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// Create lock and mark as ended
|
|
40
|
+
let resolveEnd;
|
|
41
|
+
this.endPromise = new Promise((resolve) => {
|
|
42
|
+
resolveEnd = resolve;
|
|
43
|
+
});
|
|
44
|
+
this.hasEnded = true;
|
|
45
|
+
try {
|
|
46
|
+
// Unregister from active spans
|
|
47
|
+
this.unregisterCallback(this);
|
|
48
|
+
// Calculate duration using monotonic clock
|
|
49
|
+
const endMonotonic = performance.now();
|
|
50
|
+
this.duration = endMonotonic - this.startMonotonic;
|
|
51
|
+
// Calculate end time using wall clock
|
|
52
|
+
this.endTime = this.startTime + this.duration;
|
|
53
|
+
// Convert to microseconds for native SDK
|
|
54
|
+
const startMicros = this.startTime * 1000;
|
|
55
|
+
const endMicros = this.endTime * 1000;
|
|
56
|
+
// Send to native SDK
|
|
57
|
+
await this.syncCallback(this.name, startMicros, endMicros);
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
// Release lock
|
|
61
|
+
resolveEnd();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Get the span name
|
|
66
|
+
*/
|
|
67
|
+
getName() {
|
|
68
|
+
return this.name;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if the span has ended
|
|
72
|
+
*/
|
|
73
|
+
isEnded() {
|
|
74
|
+
return this.hasEnded;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get the span duration in milliseconds (only available after end())
|
|
78
|
+
*/
|
|
79
|
+
getDuration() {
|
|
80
|
+
return this.duration;
|
|
81
|
+
}
|
|
82
|
+
}
|
package/dist/modules/APM.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CustomSpan } from '../models/CustomSpan';
|
|
1
2
|
/**
|
|
2
3
|
* Enables or disables APM
|
|
3
4
|
* @param isEnabled
|
|
@@ -80,3 +81,60 @@ export declare const _lcqSleep: () => void;
|
|
|
80
81
|
* @param isEnabled
|
|
81
82
|
*/
|
|
82
83
|
export declare const setScreenRenderingEnabled: (isEnabled: boolean) => void;
|
|
84
|
+
/**
|
|
85
|
+
* Starts a custom span for performance tracking.
|
|
86
|
+
*
|
|
87
|
+
* A custom span measures the duration of an arbitrary operation that is not
|
|
88
|
+
* automatically tracked by the SDK. The span must be manually ended by calling
|
|
89
|
+
* the `end()` method on the returned span object.
|
|
90
|
+
*
|
|
91
|
+
* @param name - The name of the span. Cannot be empty. Max 150 characters.
|
|
92
|
+
* Leading and trailing whitespace will be trimmed.
|
|
93
|
+
*
|
|
94
|
+
* @returns Promise<CustomSpan | null> - The span object to end later, or null if:
|
|
95
|
+
* - Name is empty after trimming
|
|
96
|
+
* - SDK is not initialized
|
|
97
|
+
* - APM is disabled
|
|
98
|
+
* - Custom spans feature is disabled
|
|
99
|
+
* - Maximum concurrent spans limit (100) reached
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* const span = await APM.startCustomSpan('Load User Profile');
|
|
104
|
+
* if (span) {
|
|
105
|
+
* try {
|
|
106
|
+
* // ... perform operation ...
|
|
107
|
+
* } finally {
|
|
108
|
+
* await span.end();
|
|
109
|
+
* }
|
|
110
|
+
* }
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export declare const startCustomSpan: (name: string) => Promise<CustomSpan | null>;
|
|
114
|
+
/**
|
|
115
|
+
* Records a completed custom span with pre-recorded timestamps.
|
|
116
|
+
*
|
|
117
|
+
* Use this method when you have already recorded the start and end times
|
|
118
|
+
* of an operation and want to report it retroactively.
|
|
119
|
+
*
|
|
120
|
+
* @param name - The name of the span. Cannot be empty. Max 150 characters.
|
|
121
|
+
* Leading and trailing whitespace will be trimmed.
|
|
122
|
+
* @param startDate - The start time of the operation
|
|
123
|
+
* @param endDate - The end time of the operation (must be after startDate)
|
|
124
|
+
*
|
|
125
|
+
* @returns Promise<void> - Resolves when the span has been recorded, or logs error if:
|
|
126
|
+
* - Name is empty after trimming
|
|
127
|
+
* - End date is not after start date
|
|
128
|
+
* - SDK is not initialized
|
|
129
|
+
* - APM is disabled
|
|
130
|
+
* - Custom spans feature is disabled
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```typescript
|
|
134
|
+
* const start = new Date();
|
|
135
|
+
* // ... operation already completed ...
|
|
136
|
+
* const end = new Date();
|
|
137
|
+
* await APM.addCompletedCustomSpan('Cache Lookup', start, end);
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export declare const addCompletedCustomSpan: (name: string, startDate: Date, endDate: Date) => Promise<void>;
|
package/dist/modules/APM.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Platform } from 'react-native';
|
|
2
2
|
import { NativeAPM } from '../native/NativeAPM';
|
|
3
3
|
import { NativeLuciq } from '../native/NativeLuciq';
|
|
4
|
+
import { startCustomSpan as startCustomSpanInternal, addCompletedCustomSpan as addCompletedCustomSpanInternal, } from '../utils/CustomSpansManager';
|
|
4
5
|
/**
|
|
5
6
|
* Enables or disables APM
|
|
6
7
|
* @param isEnabled
|
|
@@ -109,3 +110,64 @@ export const _lcqSleep = () => {
|
|
|
109
110
|
export const setScreenRenderingEnabled = (isEnabled) => {
|
|
110
111
|
NativeAPM.setScreenRenderingEnabled(isEnabled);
|
|
111
112
|
};
|
|
113
|
+
/**
|
|
114
|
+
* Starts a custom span for performance tracking.
|
|
115
|
+
*
|
|
116
|
+
* A custom span measures the duration of an arbitrary operation that is not
|
|
117
|
+
* automatically tracked by the SDK. The span must be manually ended by calling
|
|
118
|
+
* the `end()` method on the returned span object.
|
|
119
|
+
*
|
|
120
|
+
* @param name - The name of the span. Cannot be empty. Max 150 characters.
|
|
121
|
+
* Leading and trailing whitespace will be trimmed.
|
|
122
|
+
*
|
|
123
|
+
* @returns Promise<CustomSpan | null> - The span object to end later, or null if:
|
|
124
|
+
* - Name is empty after trimming
|
|
125
|
+
* - SDK is not initialized
|
|
126
|
+
* - APM is disabled
|
|
127
|
+
* - Custom spans feature is disabled
|
|
128
|
+
* - Maximum concurrent spans limit (100) reached
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* const span = await APM.startCustomSpan('Load User Profile');
|
|
133
|
+
* if (span) {
|
|
134
|
+
* try {
|
|
135
|
+
* // ... perform operation ...
|
|
136
|
+
* } finally {
|
|
137
|
+
* await span.end();
|
|
138
|
+
* }
|
|
139
|
+
* }
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
export const startCustomSpan = async (name) => {
|
|
143
|
+
return startCustomSpanInternal(name);
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Records a completed custom span with pre-recorded timestamps.
|
|
147
|
+
*
|
|
148
|
+
* Use this method when you have already recorded the start and end times
|
|
149
|
+
* of an operation and want to report it retroactively.
|
|
150
|
+
*
|
|
151
|
+
* @param name - The name of the span. Cannot be empty. Max 150 characters.
|
|
152
|
+
* Leading and trailing whitespace will be trimmed.
|
|
153
|
+
* @param startDate - The start time of the operation
|
|
154
|
+
* @param endDate - The end time of the operation (must be after startDate)
|
|
155
|
+
*
|
|
156
|
+
* @returns Promise<void> - Resolves when the span has been recorded, or logs error if:
|
|
157
|
+
* - Name is empty after trimming
|
|
158
|
+
* - End date is not after start date
|
|
159
|
+
* - SDK is not initialized
|
|
160
|
+
* - APM is disabled
|
|
161
|
+
* - Custom spans feature is disabled
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```typescript
|
|
165
|
+
* const start = new Date();
|
|
166
|
+
* // ... operation already completed ...
|
|
167
|
+
* const end = new Date();
|
|
168
|
+
* await APM.addCompletedCustomSpan('Cache Lookup', start, end);
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
export const addCompletedCustomSpan = async (name, startDate, endDate) => {
|
|
172
|
+
return addCompletedCustomSpanInternal(name, startDate, endDate);
|
|
173
|
+
};
|