@luciq/react-native 19.0.0 → 19.1.0-27130-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 CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased](https://github.com/luciqai/luciq-flutter-sdk/compare/v19.1.0...dev)
4
+
5
+ ### Added
6
+
7
+ - dded support for handling aborted and canceled Axios network requests. ([#36](https://github.com/luciqai/luciq-reactnative-sdk/pull/36))
8
+
9
+ ## [19.1.0](https://github.com/luciqai/luciq-reactnative-sdk/compare/v19.1.0...19.0.0)
10
+
11
+ ### Added
12
+
13
+ - Add support session replay Video-Like. ([#19](https://github.com/luciqai/luciq-reactnative-sdk/pull/19))
14
+
15
+ ### Changed
16
+
17
+ - Bump Luciq iOS SDK to v19.3.0 ([#22](https://github.com/luciqai/luciq-reactnative-sdk/pull/22)). [See release notes](https://github.com/luciqai/Luciq-iOS-sdk/releases/tag/19.3.0).
18
+
19
+ - Bump Luciq Android SDK to v19.1.0 ([#22](https://github.com/luciqai/luciq-reactnative-sdk/pull/22)). [See release notes](https://github.com/luciqai/Luciq-Android-sdk/releases/tag/v19.1.0).
20
+
3
21
  ## [19.0.0](https://github.com/luciqai/luciq-reactnative-sdk/compare/v19.0.0...dev)
4
22
 
5
23
  ### Added
@@ -44,4 +62,4 @@
44
62
 
45
63
  ## [18.0.0](https://github.com/luciqai/luciq-reactnative-sdk/compare/v18.0.0...dev) (September 24, 2025)
46
64
 
47
- - SDK rebranded from Luciq to Luciq.
65
+ - SDK rebranded from Instabug to Luciq.
@@ -1,5 +1,5 @@
1
1
  project.ext.luciq = [
2
- version: '19.0.0'
2
+ version: '19.1.0'
3
3
  ]
4
4
 
5
5
  dependencies {
@@ -65,6 +65,8 @@ final class ArgsRegistry {
65
65
  putAll(overAirUpdateService);
66
66
  putAll(autoMaskingTypes);
67
67
  putAll(userConsentActionType);
68
+ putAll(capturingModes);
69
+ putAll(screenshotQualities);
68
70
  }};
69
71
  }
70
72
 
@@ -275,4 +277,16 @@ final class ArgsRegistry {
275
277
  put("media", MaskingType.MEDIA);
276
278
  put("none", MaskingType.MASK_NOTHING);
277
279
  }};
280
+
281
+ public static final ArgsMap<Integer> capturingModes = new ArgsMap<Integer>() {{
282
+ put("capturingModeNavigation", ai.luciq.library.sessionreplay.CapturingMode.NAVIGATION);
283
+ put("capturingModeInteractions", ai.luciq.library.sessionreplay.CapturingMode.INTERACTIONS);
284
+ put("capturingModeFrequency", ai.luciq.library.sessionreplay.CapturingMode.FREQUENCY);
285
+ }};
286
+
287
+ public static final ArgsMap<Integer> screenshotQualities = new ArgsMap<Integer>() {{
288
+ put("screenshotQualityHigh", ai.luciq.library.sessionreplay.ScreenshotQuality.HIGH);
289
+ put("screenshotQualityNormal", ai.luciq.library.sessionreplay.ScreenshotQuality.NORMAL);
290
+ put("screenshotQualityGreyscale", ai.luciq.library.sessionreplay.ScreenshotQuality.GREYSCALE);
291
+ }};
278
292
  }
@@ -18,6 +18,7 @@ import ai.luciq.library.sessionreplay.SessionReplay;
18
18
  import ai.luciq.library.sessionreplay.model.SessionMetadata;
19
19
  import ai.luciq.reactlibrary.utils.EventEmitterModule;
20
20
  import ai.luciq.reactlibrary.utils.MainThreadHandler;
21
+ import android.util.Log;
21
22
  import java.util.ArrayList;
22
23
  import java.util.List;
23
24
  import java.util.concurrent.CountDownLatch;
@@ -208,6 +209,56 @@ public class RNLuciqSessionReplayModule extends EventEmitterModule {
208
209
  }
209
210
  }
210
211
 
212
+ @ReactMethod
213
+ public void setCapturingMode(final String mode) {
214
+ MainThreadHandler.runOnMainThread(new Runnable() {
215
+ @Override
216
+ public void run() {
217
+ try {
218
+ Integer capturingMode = ArgsRegistry.capturingModes.get(mode);
219
+ if (capturingMode != null) {
220
+ SessionReplay.setCapturingMode(capturingMode);
221
+ } else {
222
+ Log.w("LCQSessionReplay", "Invalid capturing mode: " + mode);
223
+ }
224
+ } catch (Exception e) {
225
+ e.printStackTrace();
226
+ }
227
+ }
228
+ });
229
+ }
230
+
231
+ @ReactMethod
232
+ public void setScreenshotQuality(final String quality) {
233
+ MainThreadHandler.runOnMainThread(new Runnable() {
234
+ @Override
235
+ public void run() {
236
+ try {
237
+ Integer screenshotQuality = ArgsRegistry.screenshotQualities.get(quality);
238
+ if (screenshotQuality != null) {
239
+ SessionReplay.setScreenshotQuality(screenshotQuality);
240
+ } else {
241
+ Log.w("LCQSessionReplay", "Invalid screenshot quality: " + quality);
242
+ }
243
+ } catch (Exception e) {
244
+ e.printStackTrace();
245
+ }
246
+ }
247
+ });
248
+ }
211
249
 
250
+ @ReactMethod
251
+ public void setScreenshotCaptureInterval(final int intervalMs) {
252
+ MainThreadHandler.runOnMainThread(new Runnable() {
253
+ @Override
254
+ public void run() {
255
+ try {
256
+ SessionReplay.setScreenshotCaptureInterval(intervalMs);
257
+ } catch (Exception e) {
258
+ e.printStackTrace();
259
+ }
260
+ }
261
+ });
262
+ }
212
263
 
213
264
  }
@@ -1,4 +1,5 @@
1
1
  import type { SessionMetadata } from '../models/SessionMetadata';
2
+ import type { CapturingMode, ScreenshotQuality } from '../utils/Enums';
2
3
  /**
3
4
  * Enables or disables Session Replay for your Luciq integration.
4
5
  *
@@ -76,3 +77,56 @@ export declare const getSessionReplayLink: () => Promise<string>;
76
77
  * ```
77
78
  */
78
79
  export declare const setSyncCallback: (handler: (payload: SessionMetadata) => boolean) => Promise<void>;
80
+ /**
81
+ * Sets the capturing mode for Session Replay screenshots.
82
+ *
83
+ * - `navigation`: Captures screenshots only when users navigate between screens (default).
84
+ * - `interactions`: Captures screenshots on screen navigation and user interactions.
85
+ * - `frequency`: Captures screenshots at a fixed time interval for video-like playback.
86
+ *
87
+ * Note: Should be called before SDK initialization for best results.
88
+ *
89
+ * @param mode The capturing mode to use.
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * import { CapturingMode } from '@luciq/react-native';
94
+ *
95
+ * SessionReplay.setCapturingMode(CapturingMode.frequency);
96
+ * ```
97
+ */
98
+ export declare const setCapturingMode: (mode: CapturingMode) => void;
99
+ /**
100
+ * Sets the visual quality of captured Session Replay screenshots.
101
+ *
102
+ * - `high`: 50% WebP compression - Best visual quality (~62 screenshots per session).
103
+ * - `normal`: 25% WebP compression - Balanced quality and storage (~104 screenshots per session, default).
104
+ * - `greyscale`: Grayscale + 25% WebP compression - Maximum storage efficiency (~130 screenshots per session).
105
+ *
106
+ * @param quality The screenshot quality profile to use.
107
+ *
108
+ * @example
109
+ * ```ts
110
+ * import { ScreenshotQuality } from '@luciq/react-native';
111
+ *
112
+ * SessionReplay.setScreenshotQuality(ScreenshotQuality.high);
113
+ * ```
114
+ */
115
+ export declare const setScreenshotQuality: (quality: ScreenshotQuality) => void;
116
+ /**
117
+ * Sets the capture interval for Session Replay when using frequency capturing mode.
118
+ *
119
+ * This determines how often screenshots are captured when `CapturingMode.frequency` is set.
120
+ *
121
+ * @param intervalMs Time between captures in milliseconds. Minimum: 500ms, Default: 1000ms.
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * // Capture every 500ms (2 FPS)
126
+ * SessionReplay.setScreenshotCaptureInterval(500);
127
+ *
128
+ * // Capture every 2 seconds
129
+ * SessionReplay.setScreenshotCaptureInterval(2000);
130
+ * ```
131
+ */
132
+ export declare const setScreenshotCaptureInterval: (intervalMs: number) => void;
@@ -96,3 +96,62 @@ export const setSyncCallback = async (handler) => {
96
96
  });
97
97
  return NativeSessionReplay.setSyncCallback();
98
98
  };
99
+ /**
100
+ * Sets the capturing mode for Session Replay screenshots.
101
+ *
102
+ * - `navigation`: Captures screenshots only when users navigate between screens (default).
103
+ * - `interactions`: Captures screenshots on screen navigation and user interactions.
104
+ * - `frequency`: Captures screenshots at a fixed time interval for video-like playback.
105
+ *
106
+ * Note: Should be called before SDK initialization for best results.
107
+ *
108
+ * @param mode The capturing mode to use.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * import { CapturingMode } from '@luciq/react-native';
113
+ *
114
+ * SessionReplay.setCapturingMode(CapturingMode.frequency);
115
+ * ```
116
+ */
117
+ export const setCapturingMode = (mode) => {
118
+ NativeSessionReplay.setCapturingMode(mode);
119
+ };
120
+ /**
121
+ * Sets the visual quality of captured Session Replay screenshots.
122
+ *
123
+ * - `high`: 50% WebP compression - Best visual quality (~62 screenshots per session).
124
+ * - `normal`: 25% WebP compression - Balanced quality and storage (~104 screenshots per session, default).
125
+ * - `greyscale`: Grayscale + 25% WebP compression - Maximum storage efficiency (~130 screenshots per session).
126
+ *
127
+ * @param quality The screenshot quality profile to use.
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * import { ScreenshotQuality } from '@luciq/react-native';
132
+ *
133
+ * SessionReplay.setScreenshotQuality(ScreenshotQuality.high);
134
+ * ```
135
+ */
136
+ export const setScreenshotQuality = (quality) => {
137
+ NativeSessionReplay.setScreenshotQuality(quality);
138
+ };
139
+ /**
140
+ * Sets the capture interval for Session Replay when using frequency capturing mode.
141
+ *
142
+ * This determines how often screenshots are captured when `CapturingMode.frequency` is set.
143
+ *
144
+ * @param intervalMs Time between captures in milliseconds. Minimum: 500ms, Default: 1000ms.
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * // Capture every 500ms (2 FPS)
149
+ * SessionReplay.setScreenshotCaptureInterval(500);
150
+ *
151
+ * // Capture every 2 seconds
152
+ * SessionReplay.setScreenshotCaptureInterval(2000);
153
+ * ```
154
+ */
155
+ export const setScreenshotCaptureInterval = (intervalMs) => {
156
+ NativeSessionReplay.setScreenshotCaptureInterval(intervalMs);
157
+ };
@@ -1,4 +1,4 @@
1
- export type NativeConstants = NativeSdkDebugLogsLevel & NativeInvocationEvent & NativeInvocationOption & NativeColorTheme & NativeFloatingButtonPosition & NativeRecordingButtonPosition & NativeWelcomeMessageMode & NativeReportType & NativeDismissType & NativeActionType & NativeExtendedBugReportMode & NativeReproStepsMode & NativeLocale & NativeNonFatalErrorLevel & NativeStringKey & NativeLaunchType & NativeOverAirUpdateServices & NativeAutoMaskingType & NativeUserConsentActionType;
1
+ export type NativeConstants = NativeSdkDebugLogsLevel & NativeInvocationEvent & NativeInvocationOption & NativeColorTheme & NativeFloatingButtonPosition & NativeRecordingButtonPosition & NativeWelcomeMessageMode & NativeReportType & NativeDismissType & NativeActionType & NativeExtendedBugReportMode & NativeReproStepsMode & NativeLocale & NativeNonFatalErrorLevel & NativeStringKey & NativeLaunchType & NativeOverAirUpdateServices & NativeAutoMaskingType & NativeUserConsentActionType & NativeCapturingMode & NativeScreenshotQuality;
2
2
  interface NativeSdkDebugLogsLevel {
3
3
  sdkDebugLogsLevelVerbose: any;
4
4
  sdkDebugLogsLevelDebug: any;
@@ -179,4 +179,14 @@ interface NativeAutoMaskingType {
179
179
  media: any;
180
180
  none: any;
181
181
  }
182
+ interface NativeCapturingMode {
183
+ capturingModeNavigation: any;
184
+ capturingModeInteractions: any;
185
+ capturingModeFrequency: any;
186
+ }
187
+ interface NativeScreenshotQuality {
188
+ screenshotQualityHigh: any;
189
+ screenshotQualityNormal: any;
190
+ screenshotQualityGreyscale: any;
191
+ }
182
192
  export {};
@@ -8,6 +8,9 @@ export interface SessionReplayNativeModule extends NativeModule {
8
8
  getSessionReplayLink(): Promise<string>;
9
9
  setSyncCallback(): Promise<void>;
10
10
  evaluateSync(shouldSync: boolean): void;
11
+ setCapturingMode(mode: any): void;
12
+ setScreenshotQuality(quality: any): void;
13
+ setScreenshotCaptureInterval(intervalMs: number): void;
11
14
  }
12
15
  export declare const NativeSessionReplay: SessionReplayNativeModule;
13
16
  export declare enum NativeEvents {
@@ -242,3 +242,40 @@ export declare enum AutoMaskingType {
242
242
  media,
243
243
  none
244
244
  }
245
+ /**
246
+ * The capturing mode for Session Replay screenshots.
247
+ */
248
+ export declare enum CapturingMode {
249
+ /**
250
+ * Captures screenshots only when users navigate between screens.
251
+ * This is the default behavior and provides the lowest overhead.
252
+ */
253
+ navigation,
254
+ /**
255
+ * Captures screenshots on screen navigation and user interactions.
256
+ * Includes debouncing to prevent excessive captures.
257
+ */
258
+ interactions,
259
+ /**
260
+ * Captures screenshots at a fixed time interval for true video-like playback.
261
+ * Also captures on screen navigation.
262
+ */
263
+ frequency
264
+ }
265
+ /**
266
+ * The quality profile for Session Replay screenshots.
267
+ */
268
+ export declare enum ScreenshotQuality {
269
+ /**
270
+ * 50% WebP compression - Best visual quality.
271
+ */
272
+ high,
273
+ /**
274
+ * 25% WebP compression - Balanced quality and storage (default).
275
+ */
276
+ normal,
277
+ /**
278
+ * Grayscale + 25% WebP compression - Maximum storage efficiency.
279
+ */
280
+ greyscale
281
+ }
@@ -264,3 +264,42 @@ export var AutoMaskingType;
264
264
  AutoMaskingType[AutoMaskingType["media"] = constants.media] = "media";
265
265
  AutoMaskingType[AutoMaskingType["none"] = constants.none] = "none";
266
266
  })(AutoMaskingType || (AutoMaskingType = {}));
267
+ /**
268
+ * The capturing mode for Session Replay screenshots.
269
+ */
270
+ export var CapturingMode;
271
+ (function (CapturingMode) {
272
+ /**
273
+ * Captures screenshots only when users navigate between screens.
274
+ * This is the default behavior and provides the lowest overhead.
275
+ */
276
+ CapturingMode[CapturingMode["navigation"] = constants.capturingModeNavigation] = "navigation";
277
+ /**
278
+ * Captures screenshots on screen navigation and user interactions.
279
+ * Includes debouncing to prevent excessive captures.
280
+ */
281
+ CapturingMode[CapturingMode["interactions"] = constants.capturingModeInteractions] = "interactions";
282
+ /**
283
+ * Captures screenshots at a fixed time interval for true video-like playback.
284
+ * Also captures on screen navigation.
285
+ */
286
+ CapturingMode[CapturingMode["frequency"] = constants.capturingModeFrequency] = "frequency";
287
+ })(CapturingMode || (CapturingMode = {}));
288
+ /**
289
+ * The quality profile for Session Replay screenshots.
290
+ */
291
+ export var ScreenshotQuality;
292
+ (function (ScreenshotQuality) {
293
+ /**
294
+ * 50% WebP compression - Best visual quality.
295
+ */
296
+ ScreenshotQuality[ScreenshotQuality["high"] = constants.screenshotQualityHigh] = "high";
297
+ /**
298
+ * 25% WebP compression - Balanced quality and storage (default).
299
+ */
300
+ ScreenshotQuality[ScreenshotQuality["normal"] = constants.screenshotQualityNormal] = "normal";
301
+ /**
302
+ * Grayscale + 25% WebP compression - Maximum storage efficiency.
303
+ */
304
+ ScreenshotQuality[ScreenshotQuality["greyscale"] = constants.screenshotQualityGreyscale] = "greyscale";
305
+ })(ScreenshotQuality || (ScreenshotQuality = {}));
@@ -159,6 +159,8 @@ export default {
159
159
  if (this._hasError) {
160
160
  cloneNetwork.errorCode = clientErrorCode;
161
161
  cloneNetwork.errorDomain = 'ClientError';
162
+ cloneNetwork.responseCode = 0;
163
+ cloneNetwork.contentType = 'text/plain';
162
164
  // @ts-ignore
163
165
  const _response = this._response;
164
166
  cloneNetwork.requestBody =
@@ -168,25 +170,32 @@ export default {
168
170
  if (typeof _response === 'string' && _response.length > 0) {
169
171
  cloneNetwork.errorDomain = _response;
170
172
  }
173
+ cloneNetwork.responseBody = `ERROR: ${cloneNetwork.errorDomain}`;
171
174
  // @ts-ignore
172
175
  }
173
176
  else if (this._timedOut) {
174
177
  cloneNetwork.errorCode = clientErrorCode;
175
- cloneNetwork.errorDomain = 'TimeOutError';
178
+ cloneNetwork.errorDomain = 'timeout';
179
+ cloneNetwork.responseCode = 0;
180
+ cloneNetwork.contentType = 'text/plain';
181
+ cloneNetwork.responseBody = `ERROR: ${cloneNetwork.errorDomain}`;
176
182
  }
177
- if (this.response) {
178
- if (this.responseType === 'blob') {
179
- const responseText = await new Response(this.response).text();
180
- cloneNetwork.responseBody = responseText;
183
+ // Only set response body if not already set by error handlers
184
+ if (!cloneNetwork.errorDomain) {
185
+ if (this.response) {
186
+ if (this.responseType === 'blob') {
187
+ const responseText = await new Response(this.response).text();
188
+ cloneNetwork.responseBody = responseText;
189
+ }
190
+ else if (['text', '', 'json'].includes(this.responseType)) {
191
+ cloneNetwork.responseBody = JSON.stringify(this.response);
192
+ }
181
193
  }
182
- else if (['text', '', 'json'].includes(this.responseType)) {
183
- cloneNetwork.responseBody = JSON.stringify(this.response);
194
+ else {
195
+ cloneNetwork.responseBody = '';
196
+ cloneNetwork.contentType = 'text/plain';
184
197
  }
185
198
  }
186
- else {
187
- cloneNetwork.responseBody = '';
188
- cloneNetwork.contentType = 'text/plain';
189
- }
190
199
  cloneNetwork.requestBodySize = cloneNetwork.requestBody.length;
191
200
  if (cloneNetwork.responseBodySize === 0 && cloneNetwork.responseBody) {
192
201
  cloneNetwork.responseBodySize = cloneNetwork.responseBody.length;
@@ -234,6 +243,17 @@ export default {
234
243
  };
235
244
  this.addEventListener('progress', downloadUploadProgressCallback);
236
245
  this.upload.addEventListener('progress', downloadUploadProgressCallback);
246
+ // Handler for abort events (works with fetch, Axios, and any XHR-based requests)
247
+ this.addEventListener('abort', () => {
248
+ if (!isInterceptorEnabled) {
249
+ return;
250
+ }
251
+ cloneNetwork.duration = Date.now() - cloneNetwork.startTime;
252
+ cloneNetwork.responseCode = 0;
253
+ cloneNetwork.errorCode = clientErrorCode;
254
+ cloneNetwork.errorDomain = 'cancelled';
255
+ cloneNetwork.responseBody = `ERROR: ${cloneNetwork.errorDomain}`;
256
+ });
237
257
  }
238
258
  cloneNetwork.startTime = Date.now();
239
259
  const traceparent = await getTraceparentHeader(cloneNetwork);
@@ -28,5 +28,7 @@ typedef NSDictionary<NSString*, NSNumber*> ArgsDictionary;
28
28
 
29
29
  + (NSDictionary<NSString *, NSString *> *) placeholders;
30
30
  + (ArgsDictionary *)autoMaskingTypes;
31
+ + (ArgsDictionary *)capturingModes;
32
+ + (ArgsDictionary *)screenshotQualities;
31
33
 
32
34
  @end
@@ -25,6 +25,8 @@
25
25
 
26
26
  [all addEntriesFromDictionary:ArgsRegistry.autoMaskingTypes];
27
27
  [all addEntriesFromDictionary:ArgsRegistry.userConsentActionTypes];
28
+ [all addEntriesFromDictionary:ArgsRegistry.capturingModes];
29
+ [all addEntriesFromDictionary:ArgsRegistry.screenshotQualities];
28
30
 
29
31
  return all;
30
32
  }
@@ -273,4 +275,20 @@
273
275
  @"none" : @(LCQAutoMaskScreenshotOptionMaskNothing)
274
276
  };
275
277
  }
278
+
279
+ + (ArgsDictionary *)capturingModes {
280
+ return @{
281
+ @"capturingModeNavigation" : @(LCQScreenshotCapturingModeNavigation),
282
+ @"capturingModeInteractions" : @(LCQScreenshotCapturingModeInteraction),
283
+ @"capturingModeFrequency" : @(LCQScreenshotCapturingModeFrequency)
284
+ };
285
+ }
286
+
287
+ + (ArgsDictionary *)screenshotQualities {
288
+ return @{
289
+ @"screenshotQualityHigh" : @(LCQScreenshotQualityModeHigh),
290
+ @"screenshotQualityNormal" : @(LCQScreenshotQualityModeNormal),
291
+ @"screenshotQualityGreyscale" : @(LCQScreenshotQualityModeGreyScale)
292
+ };
293
+ }
276
294
  @end
@@ -25,6 +25,12 @@
25
25
 
26
26
  - (void)evaluateSync:(BOOL)result;
27
27
 
28
+ - (void)setCapturingMode:(LCQScreenshotCapturingMode)mode;
29
+
30
+ - (void)setScreenshotQuality:(LCQScreenshotQualityMode)quality;
31
+
32
+ - (void)setScreenshotCaptureInterval:(NSInteger)intervalMs;
33
+
28
34
  @property (atomic, copy) SessionEvaluationCompletion sessionEvaluationCompletion;
29
35
 
30
36
  @end
@@ -95,6 +95,17 @@ RCT_EXPORT_METHOD(evaluateSync:(BOOL)result) {
95
95
  }
96
96
  }
97
97
 
98
+ RCT_EXPORT_METHOD(setCapturingMode:(LCQScreenshotCapturingMode)mode) {
99
+ LCQSessionReplay.screenshotCapturingMode = mode;
100
+ }
101
+
102
+ RCT_EXPORT_METHOD(setScreenshotQuality:(LCQScreenshotQualityMode)quality) {
103
+ LCQSessionReplay.screenshotQualityMode = quality;
104
+ }
105
+
106
+ RCT_EXPORT_METHOD(setScreenshotCaptureInterval:(NSInteger)intervalMs) {
107
+ LCQSessionReplay.screenshotCaptureInterval = intervalMs;
108
+ }
98
109
 
99
110
  @synthesize description;
100
111
 
@@ -123,5 +123,19 @@ RCT_ENUM_CONVERTER(
123
123
  integerValue
124
124
  );
125
125
 
126
+ RCT_ENUM_CONVERTER(
127
+ LCQScreenshotCapturingMode,
128
+ ArgsRegistry.capturingModes,
129
+ LCQScreenshotCapturingModeNavigation,
130
+ integerValue
131
+ );
132
+
133
+ RCT_ENUM_CONVERTER(
134
+ LCQScreenshotQualityMode,
135
+ ArgsRegistry.screenshotQualities,
136
+ LCQScreenshotQualityModeNormal,
137
+ integerValue
138
+ );
139
+
126
140
  @end
127
141
 
package/ios/native.rb CHANGED
@@ -1,4 +1,4 @@
1
- $luciq= { :version => '19.2.0' }
1
+ $luciq= { :version => '19.3.0' }
2
2
 
3
3
  def use_luciq! (spec = nil)
4
4
  version = $luciq[:version]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@luciq/react-native",
3
3
  "description": "Luciq is the Agentic Observability Platform built for Mobile.",
4
- "version": "19.0.0",
4
+ "version": "19.1.0-27130-SNAPSHOT",
5
5
  "author": "Luciq (https://luciq.ai)",
6
6
  "repository": "github:luciqai/luciq-reactnative-sdk",
7
7
  "homepage": "https://www.luciq.ai/platforms/react-native",
@@ -43,7 +43,6 @@
43
43
  },
44
44
  "devDependencies": {
45
45
  "@apollo/client": "^3.7.0",
46
- "@instabug/danger-plugin-coverage": "Instabug/danger-plugin-coverage",
47
46
  "@react-native-community/eslint-config": "^3.1.0",
48
47
  "@react-navigation/native": "^6.1.7",
49
48
  "@rollup/plugin-commonjs": "^25.0.3",
@@ -1,5 +1,6 @@
1
1
  import { NativeSessionReplay, NativeEvents, emitter } from '../native/NativeSessionReplay';
2
2
  import type { SessionMetadata } from '../models/SessionMetadata';
3
+ import type { CapturingMode, ScreenshotQuality } from '../utils/Enums';
3
4
  /**
4
5
  * Enables or disables Session Replay for your Luciq integration.
5
6
  *
@@ -109,3 +110,65 @@ export const setSyncCallback = async (
109
110
 
110
111
  return NativeSessionReplay.setSyncCallback();
111
112
  };
113
+
114
+ /**
115
+ * Sets the capturing mode for Session Replay screenshots.
116
+ *
117
+ * - `navigation`: Captures screenshots only when users navigate between screens (default).
118
+ * - `interactions`: Captures screenshots on screen navigation and user interactions.
119
+ * - `frequency`: Captures screenshots at a fixed time interval for video-like playback.
120
+ *
121
+ * Note: Should be called before SDK initialization for best results.
122
+ *
123
+ * @param mode The capturing mode to use.
124
+ *
125
+ * @example
126
+ * ```ts
127
+ * import { CapturingMode } from '@luciq/react-native';
128
+ *
129
+ * SessionReplay.setCapturingMode(CapturingMode.frequency);
130
+ * ```
131
+ */
132
+ export const setCapturingMode = (mode: CapturingMode) => {
133
+ NativeSessionReplay.setCapturingMode(mode);
134
+ };
135
+
136
+ /**
137
+ * Sets the visual quality of captured Session Replay screenshots.
138
+ *
139
+ * - `high`: 50% WebP compression - Best visual quality (~62 screenshots per session).
140
+ * - `normal`: 25% WebP compression - Balanced quality and storage (~104 screenshots per session, default).
141
+ * - `greyscale`: Grayscale + 25% WebP compression - Maximum storage efficiency (~130 screenshots per session).
142
+ *
143
+ * @param quality The screenshot quality profile to use.
144
+ *
145
+ * @example
146
+ * ```ts
147
+ * import { ScreenshotQuality } from '@luciq/react-native';
148
+ *
149
+ * SessionReplay.setScreenshotQuality(ScreenshotQuality.high);
150
+ * ```
151
+ */
152
+ export const setScreenshotQuality = (quality: ScreenshotQuality) => {
153
+ NativeSessionReplay.setScreenshotQuality(quality);
154
+ };
155
+
156
+ /**
157
+ * Sets the capture interval for Session Replay when using frequency capturing mode.
158
+ *
159
+ * This determines how often screenshots are captured when `CapturingMode.frequency` is set.
160
+ *
161
+ * @param intervalMs Time between captures in milliseconds. Minimum: 500ms, Default: 1000ms.
162
+ *
163
+ * @example
164
+ * ```ts
165
+ * // Capture every 500ms (2 FPS)
166
+ * SessionReplay.setScreenshotCaptureInterval(500);
167
+ *
168
+ * // Capture every 2 seconds
169
+ * SessionReplay.setScreenshotCaptureInterval(2000);
170
+ * ```
171
+ */
172
+ export const setScreenshotCaptureInterval = (intervalMs: number) => {
173
+ NativeSessionReplay.setScreenshotCaptureInterval(intervalMs);
174
+ };
@@ -16,7 +16,9 @@ export type NativeConstants = NativeSdkDebugLogsLevel &
16
16
  NativeLaunchType &
17
17
  NativeOverAirUpdateServices &
18
18
  NativeAutoMaskingType &
19
- NativeUserConsentActionType;
19
+ NativeUserConsentActionType &
20
+ NativeCapturingMode &
21
+ NativeScreenshotQuality;
20
22
 
21
23
  interface NativeSdkDebugLogsLevel {
22
24
  sdkDebugLogsLevelVerbose: any;
@@ -213,3 +215,15 @@ interface NativeAutoMaskingType {
213
215
  media: any;
214
216
  none: any;
215
217
  }
218
+
219
+ interface NativeCapturingMode {
220
+ capturingModeNavigation: any;
221
+ capturingModeInteractions: any;
222
+ capturingModeFrequency: any;
223
+ }
224
+
225
+ interface NativeScreenshotQuality {
226
+ screenshotQualityHigh: any;
227
+ screenshotQualityNormal: any;
228
+ screenshotQualityGreyscale: any;
229
+ }
@@ -11,6 +11,9 @@ export interface SessionReplayNativeModule extends NativeModule {
11
11
  getSessionReplayLink(): Promise<string>;
12
12
  setSyncCallback(): Promise<void>;
13
13
  evaluateSync(shouldSync: boolean): void;
14
+ setCapturingMode(mode: any): void;
15
+ setScreenshotQuality(quality: any): void;
16
+ setScreenshotCaptureInterval(intervalMs: number): void;
14
17
  }
15
18
 
16
19
  export const NativeSessionReplay = NativeModules.LCQSessionReplay;
@@ -264,3 +264,42 @@ export enum AutoMaskingType {
264
264
  media = constants.media,
265
265
  none = constants.none,
266
266
  }
267
+
268
+ /**
269
+ * The capturing mode for Session Replay screenshots.
270
+ */
271
+ export enum CapturingMode {
272
+ /**
273
+ * Captures screenshots only when users navigate between screens.
274
+ * This is the default behavior and provides the lowest overhead.
275
+ */
276
+ navigation = constants.capturingModeNavigation,
277
+ /**
278
+ * Captures screenshots on screen navigation and user interactions.
279
+ * Includes debouncing to prevent excessive captures.
280
+ */
281
+ interactions = constants.capturingModeInteractions,
282
+ /**
283
+ * Captures screenshots at a fixed time interval for true video-like playback.
284
+ * Also captures on screen navigation.
285
+ */
286
+ frequency = constants.capturingModeFrequency,
287
+ }
288
+
289
+ /**
290
+ * The quality profile for Session Replay screenshots.
291
+ */
292
+ export enum ScreenshotQuality {
293
+ /**
294
+ * 50% WebP compression - Best visual quality.
295
+ */
296
+ high = constants.screenshotQualityHigh,
297
+ /**
298
+ * 25% WebP compression - Balanced quality and storage (default).
299
+ */
300
+ normal = constants.screenshotQualityNormal,
301
+ /**
302
+ * Grayscale + 25% WebP compression - Maximum storage efficiency.
303
+ */
304
+ greyscale = constants.screenshotQualityGreyscale,
305
+ }
@@ -231,7 +231,8 @@ export default {
231
231
  if (this._hasError) {
232
232
  cloneNetwork.errorCode = clientErrorCode;
233
233
  cloneNetwork.errorDomain = 'ClientError';
234
-
234
+ cloneNetwork.responseCode = 0;
235
+ cloneNetwork.contentType = 'text/plain';
235
236
  // @ts-ignore
236
237
  const _response = this._response;
237
238
  cloneNetwork.requestBody =
@@ -243,22 +244,30 @@ export default {
243
244
  cloneNetwork.errorDomain = _response;
244
245
  }
245
246
 
247
+ cloneNetwork.responseBody = `ERROR: ${cloneNetwork.errorDomain}`;
248
+
246
249
  // @ts-ignore
247
250
  } else if (this._timedOut) {
248
251
  cloneNetwork.errorCode = clientErrorCode;
249
- cloneNetwork.errorDomain = 'TimeOutError';
252
+ cloneNetwork.errorDomain = 'timeout';
253
+ cloneNetwork.responseCode = 0;
254
+ cloneNetwork.contentType = 'text/plain';
255
+ cloneNetwork.responseBody = `ERROR: ${cloneNetwork.errorDomain}`;
250
256
  }
251
257
 
252
- if (this.response) {
253
- if (this.responseType === 'blob') {
254
- const responseText = await new Response(this.response).text();
255
- cloneNetwork.responseBody = responseText;
256
- } else if (['text', '', 'json'].includes(this.responseType)) {
257
- cloneNetwork.responseBody = JSON.stringify(this.response);
258
+ // Only set response body if not already set by error handlers
259
+ if (!cloneNetwork.errorDomain) {
260
+ if (this.response) {
261
+ if (this.responseType === 'blob') {
262
+ const responseText = await new Response(this.response).text();
263
+ cloneNetwork.responseBody = responseText;
264
+ } else if (['text', '', 'json'].includes(this.responseType)) {
265
+ cloneNetwork.responseBody = JSON.stringify(this.response);
266
+ }
267
+ } else {
268
+ cloneNetwork.responseBody = '';
269
+ cloneNetwork.contentType = 'text/plain';
258
270
  }
259
- } else {
260
- cloneNetwork.responseBody = '';
261
- cloneNetwork.contentType = 'text/plain';
262
271
  }
263
272
 
264
273
  cloneNetwork.requestBodySize = cloneNetwork.requestBody.length;
@@ -310,6 +319,18 @@ export default {
310
319
  };
311
320
  this.addEventListener('progress', downloadUploadProgressCallback);
312
321
  this.upload.addEventListener('progress', downloadUploadProgressCallback);
322
+
323
+ // Handler for abort events (works with fetch, Axios, and any XHR-based requests)
324
+ this.addEventListener('abort', () => {
325
+ if (!isInterceptorEnabled) {
326
+ return;
327
+ }
328
+ cloneNetwork.duration = Date.now() - cloneNetwork.startTime;
329
+ cloneNetwork.responseCode = 0;
330
+ cloneNetwork.errorCode = clientErrorCode;
331
+ cloneNetwork.errorDomain = 'cancelled';
332
+ cloneNetwork.responseBody = `ERROR: ${cloneNetwork.errorDomain}`;
333
+ });
313
334
  }
314
335
 
315
336
  cloneNetwork.startTime = Date.now();