@coralogix/react-native-plugin 0.10.0 → 0.10.2

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,3 +1,22 @@
1
+ ## 0.10.2 (2026-08-25)
2
+
3
+ ### 🩹 Fixes
4
+
5
+ - `sessionSampleRate` is now applied once instead of twice — the configured percentage was being rolled on both the JavaScript and native sides, so a rate of 1% collected roughly 0.01% of sessions
6
+ - A fractional `sessionSampleRate` such as `0.5` is now rounded to a whole percentage instead of being truncated to `0` on iOS, which silently sampled out every session
7
+ - `SessionReplay.init()` now resolves `false` instead of rejecting when the session was sampled out, and resolves a real boolean on both platforms
8
+
9
+ ## 0.10.1 (2026-08-11)
10
+
11
+ ### 🩹 Fixes
12
+
13
+ - Network request events on iOS now report the real request duration, response body size, and status text — the duration value was previously written into the response-size field and never reached the SDK, and status text was not forwarded at all
14
+ - Network request events no longer carry an error-message field on any platform, matching the event schema — error details are reported through error events only
15
+
16
+ ### ⬆️ Dependency Updates
17
+
18
+ - Native iOS SDK bumped to **2.15.1** — network events reported from the JavaScript layer now honor the measured request duration and status text
19
+
1
20
  ## 0.10.0 (2026-08-10)
2
21
 
3
22
  ### ⬆️ Dependency Updates
package/CxSdk.podspec CHANGED
@@ -16,9 +16,9 @@ Pod::Spec.new do |s|
16
16
 
17
17
  s.source_files = "ios/**/*.{h,m,mm,swift}"
18
18
 
19
- s.dependency 'Coralogix','2.14.0'
20
- s.dependency 'CoralogixInternal','2.14.0'
21
- s.dependency 'SessionReplay','2.14.0'
19
+ s.dependency 'Coralogix','2.15.1'
20
+ s.dependency 'CoralogixInternal','2.15.1'
21
+ s.dependency 'SessionReplay','2.15.1'
22
22
 
23
23
  # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
24
24
  # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
package/README.md CHANGED
@@ -12,7 +12,7 @@ This package replaces the old `@coralogix/react-native-sdk` package.
12
12
 
13
13
  To use Coralogix SDK, call `CoralogixRum.init(options)` at the soonest available moment after the app loads.
14
14
  This will initialize the SDK based on the options you provided.
15
- Please note that `init` is an `async` function.
15
+ `init` is an `async` function.
16
16
 
17
17
  ```javascript
18
18
  import { CoralogixRum } from '@coralogix/react-native-plugin'
@@ -32,6 +32,11 @@ await CoralogixRum.init({
32
32
  });
33
33
  ```
34
34
 
35
+ `sessionSampleRate` is a whole percentage in `0`-`100` and is applied **once**. A
36
+ fractional value is rounded to an integer before it is forwarded (and a non-zero
37
+ rate is never rounded down to `0`), because the native bridges read the value as
38
+ an integer.
39
+
35
40
  To provide contextual information or transmit manual logs, utilize the exported functions of `CoralogixRum`.
36
41
  Keep in mind that these functions will remain inactive until you've invoked `CoralogixRum.init()`.
37
42
 
@@ -67,6 +72,7 @@ CoralogixRum.log(CoralogixLogSeverity.Error, 'this is a log', { key: 'value' });
67
72
  CoralogixRum.error('this is a log with error severity', { key: 'value' });
68
73
  ```
69
74
 
75
+ <!-- split title="Logs, errors, and views" path="features/logs-errors-and-views.md" -->
70
76
  ### Custom Logs
71
77
 
72
78
  Send structured logs with optional data and labels.
@@ -80,9 +86,10 @@ CoralogixRum.debug('this is a debug log', {key: 'value', pi: 3.14});
80
86
  CoralogixRum.error('this is an error log', {error: 'yes', is_bad: 'no'});
81
87
  ```
82
88
 
83
- > **Note:** Due to ReactNative limitations, not all value types are supported for the log data values,
84
- > our testing shows that ReactNative can't pass some types to the coralogix native layer, types we have identified as problematic are `Map`, `Set`, `Date` and `Function`
85
- > please note that there could be more, so for example:
89
+ > [!NOTE]
90
+ > Due to React Native limitations, not all value types are supported for the log data values.
91
+ > Our testing shows that React Native can't pass some types to the coralogix native layer; the ones we have identified as problematic are `Map`, `Set`, `Date` and `Function`.
92
+ > There could be more. For example:
86
93
  > ```javascript
87
94
  > // this would pass as an empty object to the coralogix native layer, same for Set and Date:
88
95
  > CoralogixRum.debug('this will be a problem', {key: new Map().set(1, 2)});
@@ -90,7 +97,7 @@ CoralogixRum.error('this is an error log', {error: 'yes', is_bad: 'no'});
90
97
  > // this would pass as null to the coralogix native layer
91
98
  > CoralogixRum.debug('this will be a problem too', {key: () => {}});
92
99
  > ```
93
- > in the event that you need to use any of these types please stringify them before you pass it to the `log` method
100
+ > If you need to pass any of these types, stringify them before you pass them to the `log` method.
94
101
 
95
102
  ### Error Reporting
96
103
 
@@ -193,6 +200,8 @@ subsequent events belong to a fresh session with a new session id.
193
200
  CoralogixRum.createNewSession();
194
201
  ```
195
202
 
203
+ <!-- /split -->
204
+ <!-- split title="Configuration options" path="configuration/options.md" -->
196
205
  ### Instrumentation's
197
206
 
198
207
  Turn on/off specific instrumentation; defaults to true for all.
@@ -282,6 +291,8 @@ await CoralogixRum.init({
282
291
  });
283
292
  ```
284
293
 
294
+ <!-- /split -->
295
+ <!-- split title="Custom instrumentation" path="features/custom-instrumentation.md" -->
285
296
  ### Custom Spans
286
297
 
287
298
  Create manual RUM spans to instrument custom flows in your application. Requires `traceParentInHeader.enabled: true`.
@@ -331,7 +342,8 @@ Pass instrument names to prevent network requests, errors, or interactions fired
331
342
  const tracer = CoralogixRum.getCustomTracer(['networkRequests', 'userInteractions', 'errors']);
332
343
  ```
333
344
 
334
- > **Note:** Only one global span may be active at a time. `startGlobalSpan` returns `null` if a global span is already open.
345
+ > [!NOTE]
346
+ > Only one global span may be active at a time. `startGlobalSpan` returns `null` if a global span is already open.
335
347
 
336
348
  ### Time Measurement
337
349
 
@@ -357,6 +369,8 @@ CoralogixRum.endTimeMeasure('checkout');
357
369
  - Unmatched `endTimeMeasure` calls are dropped silently by the native SDK.
358
370
  - Calling `startTimeMeasure` again with an open `name` overwrites the previous start.
359
371
 
372
+ <!-- /split -->
373
+ <!-- split title="Exporting and networking" path="configuration/exporting-and-networking.md" -->
360
374
  ### Traces Exporter
361
375
 
362
376
  Receive OTLP-formatted trace batches from the native SDK in your JavaScript code. Use this to forward spans to an OTLP-compatible backend (e.g. Jaeger, custom collector) alongside Coralogix.
@@ -400,7 +414,8 @@ The callback receives a `TraceExporterData` object following the OTLP JSON forma
400
414
  }
401
415
  ```
402
416
 
403
- > **Note:** The callback fires once per native export batch, which typically contains several spans rather than one per span.
417
+ > [!NOTE]
418
+ > The callback fires once per native export batch, which typically contains several spans rather than one per span.
404
419
 
405
420
  ### beforeSend
406
421
 
@@ -454,7 +469,8 @@ await CoralogixRum.init({
454
469
  });
455
470
  ```
456
471
 
457
- > **Note:** always read it as `isSessionSampledIn ?? true`. The field is absent on
472
+ > [!NOTE]
473
+ > Always read it as `isSessionSampledIn ?? true`. The field is absent on
458
474
  > native SDKs that predate it, and the SDK never invents a value it was not given.
459
475
 
460
476
  `isSessionSampledIn` is read-only: it reports the SDK's sampling decision for the
@@ -515,6 +531,8 @@ await CoralogixRum.init({
515
531
  });
516
532
  ```
517
533
 
534
+ <!-- /split -->
535
+ <!-- split title="Session Replay" path="features/session-replay.md" -->
518
536
  ### Session Replay
519
537
 
520
538
  Session Replay allows you to record and replay user sessions to understand user behavior and debug issues.
@@ -616,6 +634,7 @@ import { SessionReplay } from '@coralogix/react-native-plugin';
616
634
  The `SessionReplay.maskView` function accepts a `LayoutChangeEvent` and will mask the view in session replay recordings. It works on both the legacy and the New Architecture (Fabric).
617
635
 
618
636
 
637
+ <!-- /split -->
619
638
  ### Optional - Coralogix Gradle Plugin (Android)
620
639
 
621
640
  The Coralogix Gradle Plugin automatically instruments all OkHttp clients in your app (including third-party SDKs) at build time.
@@ -856,7 +856,9 @@ class CxSdkModule(reactContext: ReactApplicationContext) :
856
856
  duration = if (hasKey("duration")) getDouble("duration").toLong() else 0,
857
857
  responseContentLength = if (hasKey("responseContentLength"))
858
858
  getDouble("responseContentLength").toLong() else 0,
859
- errorMessage = getString("errorMessage"),
859
+ // Intentionally not read from the bridge: the network event schema has no
860
+ // error_message field — error details belong to error events only.
861
+ errorMessage = null,
860
862
  traceId = getString("customTraceId"),
861
863
  spanId = getString("customSpanId"),
862
864
  requestHeaders = if (hasKey("request_headers") && !isNull("request_headers"))
package/index.cjs.js CHANGED
@@ -577,7 +577,7 @@ function stopJsRefreshRateSampler() {
577
577
  appStateSub = null;
578
578
  }
579
579
 
580
- var version = "0.10.0";
580
+ var version = "0.10.2";
581
581
  var pkg = {
582
582
  version: version};
583
583
 
@@ -766,9 +766,43 @@ const getUrlFragments = url => {
766
766
  return url;
767
767
  }
768
768
  };
769
+
770
+ /**
771
+ * Rolls the session sampling dice.
772
+ *
773
+ * This is the only roll for a config without `excludeFromSampling`: `init` pins the
774
+ * rate it sends to native to 100 in that case, so native does not roll again. See the
775
+ * comment in `CoralogixRum.init` for why the exclude case is handled the other way round.
776
+ */
769
777
  function isSamplingOn(threshold) {
770
778
  return threshold > 0 && Math.random() * 100 <= threshold;
771
779
  }
780
+
781
+ /**
782
+ * Normalizes `sessionSampleRate` into the shape both native SDKs expect: an
783
+ * integer percentage in [0, 100].
784
+ *
785
+ * The bridges read the value as an integer (`NSNumber.intValue` on iOS,
786
+ * `ReadableMap.getInt` on Android), so a fractional rate such as `0.5` would be
787
+ * truncated to `0` on iOS — silently sampling every session out — and can throw
788
+ * on Android. Round instead, and never round a caller-requested non-zero rate
789
+ * down to `0`: `0` is the one value that means "emit nothing".
790
+ */
791
+ function normalizeSessionSampleRate(rate) {
792
+ if (typeof rate !== 'number' || !Number.isFinite(rate)) {
793
+ logger.warn(`CoralogixRum: \`sessionSampleRate\` must be a finite number; received ${String(rate)}. Falling back to 100.`);
794
+ return 100;
795
+ }
796
+ if (rate <= 0) {
797
+ return 0;
798
+ }
799
+ if (rate >= 100) {
800
+ return 100;
801
+ }
802
+
803
+ // Keep a sub-1% request alive rather than truncating it to "emit nothing".
804
+ return Math.max(1, Math.round(rate));
805
+ }
772
806
  function resolveCoralogixOtelWebConfig(config) {
773
807
  var _config$sessionSample, _config$view_context;
774
808
  const {
@@ -780,7 +814,7 @@ function resolveCoralogixOtelWebConfig(config) {
780
814
  OPTIONS_DEFAULTS.ignoreUrls && resolvedIgnoreUrls.push(...OPTIONS_DEFAULTS.ignoreUrls);
781
815
  return _extends({}, OPTIONS_DEFAULTS, config, {
782
816
  ignoreUrls: resolvedIgnoreUrls,
783
- sessionSampleRate: (_config$sessionSample = config.sessionSampleRate) != null ? _config$sessionSample : 100,
817
+ sessionSampleRate: normalizeSessionSampleRate((_config$sessionSample = config.sessionSampleRate) != null ? _config$sessionSample : 100),
784
818
  debug,
785
819
  view_context: (_config$view_context = config.view_context) != null ? _config$view_context : OPTIONS_DEFAULTS.view_context
786
820
  });
@@ -870,7 +904,6 @@ class CoralogixFetchInstrumentation extends instrumentationFetch.FetchInstrument
870
904
  const responseContentLength = attrs[OtelNetworkAttrs.RESPONSE_CONTENT_LENGTH] || 0;
871
905
  const duration = hrTimeToMilliseconds(readableSpan.duration);
872
906
  const fragments = getUrlFragments(url);
873
- const errorMessage = result.message;
874
907
  const customTraceId = readableSpan.spanContext().traceId;
875
908
  const customSpanId = readableSpan.spanContext().spanId;
876
909
  const parsed = new URL(url);
@@ -887,7 +920,6 @@ class CoralogixFetchInstrumentation extends instrumentationFetch.FetchInstrument
887
920
  statusText,
888
921
  duration,
889
922
  responseContentLength,
890
- errorMessage,
891
923
  customTraceId,
892
924
  customSpanId
893
925
  };
@@ -1035,6 +1067,9 @@ function createErrorProxy(errorMessage) {
1035
1067
  });
1036
1068
  }
1037
1069
  let isInited = false;
1070
+ // Whether native, rather than JS, rolled the sampling for this init. Only in that mode can
1071
+ // `CoralogixRum.isInited` be true while native has no RUM behind it.
1072
+ let nativeOwnsSamplingRoll = false;
1038
1073
  let fetchInstrumentation;
1039
1074
  function toSafeRecord(name, value) {
1040
1075
  if (value == null) {
@@ -1051,7 +1086,7 @@ const CoralogixRum = {
1051
1086
  return isInited;
1052
1087
  },
1053
1088
  init: async function (options) {
1054
- var _resolvedOptions$debu, _resolvedOptions$excl, _resolvedOptions$excl2;
1089
+ var _options$debug, _resolvedOptions$excl, _resolvedOptions$excl2;
1055
1090
  if (isInited) {
1056
1091
  console.warn('[Coralogix React Native SDK] - already initialized');
1057
1092
  return;
@@ -1060,13 +1095,28 @@ const CoralogixRum = {
1060
1095
  console.warn('[Coralogix React Native SDK] - `public_key` is required and must be a non-empty string; initialization aborted.');
1061
1096
  return;
1062
1097
  }
1098
+
1099
+ // Configure the logger first: resolving the options normalizes `sessionSampleRate`,
1100
+ // which warns about an invalid rate. Doing it the other way round swallowed that
1101
+ // warning on the first init — exactly when the caller needs to see it.
1102
+ logger.shouldLog = (_options$debug = options.debug) != null ? _options$debug : false;
1063
1103
  const resolvedOptions = resolveCoralogixOtelWebConfig(options);
1064
- logger.shouldLog = (_resolvedOptions$debu = resolvedOptions.debug) != null ? _resolvedOptions$debu : false;
1104
+
1105
+ // The rate has to be applied exactly *once*. Rolling it on both sides squared it:
1106
+ // `sessionSampleRate: 1` kept ~0.01% of sessions instead of 1%.
1107
+ //
1108
+ // Without `excludeFromSampling`, JS rolls and the rate sent to native is pinned to 100
1109
+ // so native does not roll again; a losing roll returns here, which also keeps JS
1110
+ // instrumentation off a session that would emit nothing. With `excludeFromSampling`,
1111
+ // only native can honour "sampled out, but still emit these categories", so JS does not
1112
+ // roll and forwards the real rate — pinning to 100 there would sample every session in.
1065
1113
  const hasExcludeOverrides = ((_resolvedOptions$excl = (_resolvedOptions$excl2 = resolvedOptions.excludeFromSampling) == null ? void 0 : _resolvedOptions$excl2.length) != null ? _resolvedOptions$excl : 0) > 0;
1066
- if (!isSamplingOn(resolvedOptions.sessionSampleRate) && !hasExcludeOverrides) {
1114
+ if (!hasExcludeOverrides && !isSamplingOn(resolvedOptions.sessionSampleRate)) {
1067
1115
  logger.debug('CoralogixRum: Session tracking is disabled');
1068
1116
  return;
1069
1117
  }
1118
+ nativeOwnsSamplingRoll = hasExcludeOverrides;
1119
+ const nativeSessionSampleRate = hasExcludeOverrides ? resolvedOptions.sessionSampleRate : 100;
1070
1120
  await registerCoralogixInstrumentations(resolvedOptions);
1071
1121
  let finalOptions;
1072
1122
  const lastNavRoute = getLastNavigationRouteDetected();
@@ -1097,6 +1147,7 @@ const CoralogixRum = {
1097
1147
  });
1098
1148
  }
1099
1149
  await CxSdk.initialize(_extends({}, finalOptions, {
1150
+ sessionSampleRate: nativeSessionSampleRate,
1100
1151
  frameworkVersion: pkg.version,
1101
1152
  networkExtraConfig: finalOptions.networkExtraConfig ? serializeNetworkCaptureRules(finalOptions.networkExtraConfig) : undefined,
1102
1153
  hasBeforeSend: !!finalOptions.beforeSend,
@@ -1282,7 +1333,21 @@ const SessionReplay = {
1282
1333
  logger.warn("invalid options in SessionReplay.init: ", options);
1283
1334
  return false;
1284
1335
  }
1285
- return await CxSdk.initializeSessionReplay(options);
1336
+ try {
1337
+ // iOS resolves the string "initializeSessionReplay success" rather than a boolean,
1338
+ // so coerce instead of handing a truthy string back through a `Promise<boolean>`.
1339
+ return !!(await CxSdk.initializeSessionReplay(options));
1340
+ } catch (e) {
1341
+ // When native owns the sampling roll, a sampled-out session leaves no native RUM
1342
+ // behind and iOS rejects rather than resolving. That is an expected outcome, not a
1343
+ // failure, so report it as `false` like the guards above rather than surfacing an
1344
+ // unhandled rejection. Every other rejection is a real error and stays the caller's.
1345
+ if (!nativeOwnsSamplingRoll) {
1346
+ throw e;
1347
+ }
1348
+ logger.warn('SessionReplay.init: no native RUM for this session (sampled out); replay not started.', e);
1349
+ return false;
1350
+ }
1286
1351
  },
1287
1352
  shutdown: async () => {
1288
1353
  logger.debug("session replay: shutdown called");
package/index.esm.js CHANGED
@@ -575,7 +575,7 @@ function stopJsRefreshRateSampler() {
575
575
  appStateSub = null;
576
576
  }
577
577
 
578
- var version = "0.10.0";
578
+ var version = "0.10.2";
579
579
  var pkg = {
580
580
  version: version};
581
581
 
@@ -764,9 +764,43 @@ const getUrlFragments = url => {
764
764
  return url;
765
765
  }
766
766
  };
767
+
768
+ /**
769
+ * Rolls the session sampling dice.
770
+ *
771
+ * This is the only roll for a config without `excludeFromSampling`: `init` pins the
772
+ * rate it sends to native to 100 in that case, so native does not roll again. See the
773
+ * comment in `CoralogixRum.init` for why the exclude case is handled the other way round.
774
+ */
767
775
  function isSamplingOn(threshold) {
768
776
  return threshold > 0 && Math.random() * 100 <= threshold;
769
777
  }
778
+
779
+ /**
780
+ * Normalizes `sessionSampleRate` into the shape both native SDKs expect: an
781
+ * integer percentage in [0, 100].
782
+ *
783
+ * The bridges read the value as an integer (`NSNumber.intValue` on iOS,
784
+ * `ReadableMap.getInt` on Android), so a fractional rate such as `0.5` would be
785
+ * truncated to `0` on iOS — silently sampling every session out — and can throw
786
+ * on Android. Round instead, and never round a caller-requested non-zero rate
787
+ * down to `0`: `0` is the one value that means "emit nothing".
788
+ */
789
+ function normalizeSessionSampleRate(rate) {
790
+ if (typeof rate !== 'number' || !Number.isFinite(rate)) {
791
+ logger.warn(`CoralogixRum: \`sessionSampleRate\` must be a finite number; received ${String(rate)}. Falling back to 100.`);
792
+ return 100;
793
+ }
794
+ if (rate <= 0) {
795
+ return 0;
796
+ }
797
+ if (rate >= 100) {
798
+ return 100;
799
+ }
800
+
801
+ // Keep a sub-1% request alive rather than truncating it to "emit nothing".
802
+ return Math.max(1, Math.round(rate));
803
+ }
770
804
  function resolveCoralogixOtelWebConfig(config) {
771
805
  var _config$sessionSample, _config$view_context;
772
806
  const {
@@ -778,7 +812,7 @@ function resolveCoralogixOtelWebConfig(config) {
778
812
  OPTIONS_DEFAULTS.ignoreUrls && resolvedIgnoreUrls.push(...OPTIONS_DEFAULTS.ignoreUrls);
779
813
  return _extends({}, OPTIONS_DEFAULTS, config, {
780
814
  ignoreUrls: resolvedIgnoreUrls,
781
- sessionSampleRate: (_config$sessionSample = config.sessionSampleRate) != null ? _config$sessionSample : 100,
815
+ sessionSampleRate: normalizeSessionSampleRate((_config$sessionSample = config.sessionSampleRate) != null ? _config$sessionSample : 100),
782
816
  debug,
783
817
  view_context: (_config$view_context = config.view_context) != null ? _config$view_context : OPTIONS_DEFAULTS.view_context
784
818
  });
@@ -868,7 +902,6 @@ class CoralogixFetchInstrumentation extends FetchInstrumentation {
868
902
  const responseContentLength = attrs[OtelNetworkAttrs.RESPONSE_CONTENT_LENGTH] || 0;
869
903
  const duration = hrTimeToMilliseconds(readableSpan.duration);
870
904
  const fragments = getUrlFragments(url);
871
- const errorMessage = result.message;
872
905
  const customTraceId = readableSpan.spanContext().traceId;
873
906
  const customSpanId = readableSpan.spanContext().spanId;
874
907
  const parsed = new URL(url);
@@ -885,7 +918,6 @@ class CoralogixFetchInstrumentation extends FetchInstrumentation {
885
918
  statusText,
886
919
  duration,
887
920
  responseContentLength,
888
- errorMessage,
889
921
  customTraceId,
890
922
  customSpanId
891
923
  };
@@ -1033,6 +1065,9 @@ function createErrorProxy(errorMessage) {
1033
1065
  });
1034
1066
  }
1035
1067
  let isInited = false;
1068
+ // Whether native, rather than JS, rolled the sampling for this init. Only in that mode can
1069
+ // `CoralogixRum.isInited` be true while native has no RUM behind it.
1070
+ let nativeOwnsSamplingRoll = false;
1036
1071
  let fetchInstrumentation;
1037
1072
  function toSafeRecord(name, value) {
1038
1073
  if (value == null) {
@@ -1049,7 +1084,7 @@ const CoralogixRum = {
1049
1084
  return isInited;
1050
1085
  },
1051
1086
  init: async function (options) {
1052
- var _resolvedOptions$debu, _resolvedOptions$excl, _resolvedOptions$excl2;
1087
+ var _options$debug, _resolvedOptions$excl, _resolvedOptions$excl2;
1053
1088
  if (isInited) {
1054
1089
  console.warn('[Coralogix React Native SDK] - already initialized');
1055
1090
  return;
@@ -1058,13 +1093,28 @@ const CoralogixRum = {
1058
1093
  console.warn('[Coralogix React Native SDK] - `public_key` is required and must be a non-empty string; initialization aborted.');
1059
1094
  return;
1060
1095
  }
1096
+
1097
+ // Configure the logger first: resolving the options normalizes `sessionSampleRate`,
1098
+ // which warns about an invalid rate. Doing it the other way round swallowed that
1099
+ // warning on the first init — exactly when the caller needs to see it.
1100
+ logger.shouldLog = (_options$debug = options.debug) != null ? _options$debug : false;
1061
1101
  const resolvedOptions = resolveCoralogixOtelWebConfig(options);
1062
- logger.shouldLog = (_resolvedOptions$debu = resolvedOptions.debug) != null ? _resolvedOptions$debu : false;
1102
+
1103
+ // The rate has to be applied exactly *once*. Rolling it on both sides squared it:
1104
+ // `sessionSampleRate: 1` kept ~0.01% of sessions instead of 1%.
1105
+ //
1106
+ // Without `excludeFromSampling`, JS rolls and the rate sent to native is pinned to 100
1107
+ // so native does not roll again; a losing roll returns here, which also keeps JS
1108
+ // instrumentation off a session that would emit nothing. With `excludeFromSampling`,
1109
+ // only native can honour "sampled out, but still emit these categories", so JS does not
1110
+ // roll and forwards the real rate — pinning to 100 there would sample every session in.
1063
1111
  const hasExcludeOverrides = ((_resolvedOptions$excl = (_resolvedOptions$excl2 = resolvedOptions.excludeFromSampling) == null ? void 0 : _resolvedOptions$excl2.length) != null ? _resolvedOptions$excl : 0) > 0;
1064
- if (!isSamplingOn(resolvedOptions.sessionSampleRate) && !hasExcludeOverrides) {
1112
+ if (!hasExcludeOverrides && !isSamplingOn(resolvedOptions.sessionSampleRate)) {
1065
1113
  logger.debug('CoralogixRum: Session tracking is disabled');
1066
1114
  return;
1067
1115
  }
1116
+ nativeOwnsSamplingRoll = hasExcludeOverrides;
1117
+ const nativeSessionSampleRate = hasExcludeOverrides ? resolvedOptions.sessionSampleRate : 100;
1068
1118
  await registerCoralogixInstrumentations(resolvedOptions);
1069
1119
  let finalOptions;
1070
1120
  const lastNavRoute = getLastNavigationRouteDetected();
@@ -1095,6 +1145,7 @@ const CoralogixRum = {
1095
1145
  });
1096
1146
  }
1097
1147
  await CxSdk.initialize(_extends({}, finalOptions, {
1148
+ sessionSampleRate: nativeSessionSampleRate,
1098
1149
  frameworkVersion: pkg.version,
1099
1150
  networkExtraConfig: finalOptions.networkExtraConfig ? serializeNetworkCaptureRules(finalOptions.networkExtraConfig) : undefined,
1100
1151
  hasBeforeSend: !!finalOptions.beforeSend,
@@ -1280,7 +1331,21 @@ const SessionReplay = {
1280
1331
  logger.warn("invalid options in SessionReplay.init: ", options);
1281
1332
  return false;
1282
1333
  }
1283
- return await CxSdk.initializeSessionReplay(options);
1334
+ try {
1335
+ // iOS resolves the string "initializeSessionReplay success" rather than a boolean,
1336
+ // so coerce instead of handing a truthy string back through a `Promise<boolean>`.
1337
+ return !!(await CxSdk.initializeSessionReplay(options));
1338
+ } catch (e) {
1339
+ // When native owns the sampling roll, a sampled-out session leaves no native RUM
1340
+ // behind and iOS rejects rather than resolving. That is an expected outcome, not a
1341
+ // failure, so report it as `false` like the guards above rather than surfacing an
1342
+ // unhandled rejection. Every other rejection is a real error and stays the caller's.
1343
+ if (!nativeOwnsSamplingRoll) {
1344
+ throw e;
1345
+ }
1346
+ logger.warn('SessionReplay.init: no native RUM for this session (sampled out); replay not started.', e);
1347
+ return false;
1348
+ }
1284
1349
  },
1285
1350
  shutdown: async () => {
1286
1351
  logger.debug("session replay: shutdown called");
package/ios/CxSdk.swift CHANGED
@@ -175,7 +175,6 @@ class CxSdk: RCTEventEmitter {
175
175
  }
176
176
 
177
177
  let cxNetworkDict = toCoralogixNetwork(dictionary: dictionary)
178
- print("[CX DEBUG] cxNetworkDict response_payload: \( cxNetworkDict)")
179
178
  coralogixRum?.setNetworkRequestContext(dictionary: cxNetworkDict)
180
179
  resolve("reportNetworkRequest success")
181
180
  }
@@ -471,12 +470,17 @@ class CxSdk: RCTEventEmitter {
471
470
  result["url"] = dictionary["url"] as? String ?? ""
472
471
  result["host"] = dictionary["host"] as? String ?? ""
473
472
  result["method"] = dictionary["method"] as? String ?? ""
474
- result["status_code"] = dictionary["statusCode"] as? Int ?? 0
475
- result["http_response_body_size"] = dictionary["duration"] as? Int ?? 0
473
+ // JS numbers cross the bridge as NSNumber doubles; coerce instead of an exact-Int cast.
474
+ result["status_code"] = (dictionary["statusCode"] as? NSNumber)?.intValue ?? 0
475
+ result["status_text"] = dictionary["statusText"] as? String ?? ""
476
+ result["duration"] = (dictionary["duration"] as? NSNumber)?.doubleValue ?? 0
477
+ result["http_response_body_size"] = (dictionary["responseContentLength"] as? NSNumber)?.intValue ?? 0
476
478
  result["fragments"] = dictionary["fragments"] as? String ?? ""
477
479
  result["schema"] = dictionary["schema"] as? String ?? ""
478
480
  result["customTraceId"] = dictionary["customTraceId"] as? String ?? ""
479
481
  result["customSpanId"] = dictionary["customSpanId"] as? String ?? ""
482
+ // errorMessage is intentionally not forwarded: the network event schema has no
483
+ // error_message field — error details belong to error events only.
480
484
  if let v = dictionary["request_headers"] as? [String: String] { result["request_headers"] = v }
481
485
  if let v = dictionary["response_headers"] as? [String: String] { result["response_headers"] = v }
482
486
  if let v = dictionary["request_payload"] as? String { result["request_payload"] = v }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coralogix/react-native-plugin",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "description": "Official Coralogix React Native plugin",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Coralogix",
@@ -8,6 +8,11 @@ export type NetworkRequestDetails = {
8
8
  statusText: string;
9
9
  duration: number;
10
10
  responseContentLength: number;
11
+ /**
12
+ * @deprecated Ignored. The network event schema has no error field — error
13
+ * details are reported through error events only. Kept for source
14
+ * compatibility; neither platform forwards it.
15
+ */
11
16
  errorMessage?: string | null;
12
17
  customTraceId: string;
13
18
  customSpanId: string;
@@ -260,7 +260,17 @@ export interface CoralogixBrowserSdkConfig {
260
260
  fpsSamplingSeconds?: number;
261
261
  /** Sets a value for the `environment` attribute */
262
262
  environment?: string;
263
- /** Percentage of overall sessions being tracked, defaults to 100% */
263
+ /**
264
+ * Percentage of overall sessions being tracked, defaults to 100%.
265
+ *
266
+ * The rate is applied exactly once. Without `excludeFromSampling` the plugin
267
+ * rolls it and the native SDK is told 100; with `excludeFromSampling` set the
268
+ * native SDK rolls it, since only native can emit the excluded categories for a
269
+ * sampled-out session.
270
+ *
271
+ * Expressed as a whole percentage in [0, 100]; a fractional value is rounded
272
+ * (never down to 0) because the native bridges read it as an integer.
273
+ */
264
274
  sessionSampleRate?: number;
265
275
  /**
266
276
  * Event categories that bypass session sampling. Events of these categories are
package/src/utils.d.ts CHANGED
@@ -1,5 +1,23 @@
1
1
  import { CoralogixBrowserSdkConfig } from './model/Types';
2
2
  export declare const hrTimeToMilliseconds: ([seconds, nanoseconds]: [number, number]) => number;
3
3
  export declare const getUrlFragments: (url: string) => string;
4
+ /**
5
+ * Rolls the session sampling dice.
6
+ *
7
+ * This is the only roll for a config without `excludeFromSampling`: `init` pins the
8
+ * rate it sends to native to 100 in that case, so native does not roll again. See the
9
+ * comment in `CoralogixRum.init` for why the exclude case is handled the other way round.
10
+ */
4
11
  export declare function isSamplingOn(threshold: number): boolean;
12
+ /**
13
+ * Normalizes `sessionSampleRate` into the shape both native SDKs expect: an
14
+ * integer percentage in [0, 100].
15
+ *
16
+ * The bridges read the value as an integer (`NSNumber.intValue` on iOS,
17
+ * `ReadableMap.getInt` on Android), so a fractional rate such as `0.5` would be
18
+ * truncated to `0` on iOS — silently sampling every session out — and can throw
19
+ * on Android. Round instead, and never round a caller-requested non-zero rate
20
+ * down to `0`: `0` is the one value that means "emit nothing".
21
+ */
22
+ export declare function normalizeSessionSampleRate(rate: unknown): number;
5
23
  export declare function resolveCoralogixOtelWebConfig(config: CoralogixBrowserSdkConfig): CoralogixBrowserSdkConfig;