@coralogix/react-native-plugin 0.2.10 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.esm.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { AppState, Platform, NativeModules, NativeEventEmitter, findNodeHandle } from 'react-native';
2
- import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';
2
+ import React from 'react';
3
3
  import { InstrumentationBase, registerInstrumentations } from '@opentelemetry/instrumentation';
4
+ import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';
4
5
  import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
5
6
  import { W3CTraceContextPropagator } from '@opentelemetry/core';
6
7
 
@@ -124,6 +125,308 @@ class CoralogixErrorInstrumentation extends InstrumentationBase {
124
125
  disable() {}
125
126
  }
126
127
 
128
+ const USER_INTERACTION_INSTRUMENTATION_NAME = 'user-interaction';
129
+ const USER_INTERACTION_INSTRUMENTATION_VERSION = '1';
130
+
131
+ // After onScrollEndDrag, how long to wait before classifying as scroll.
132
+ // If onMomentumScrollBegin fires within this window, it's a swipe instead.
133
+ const SWIPE_MOMENTUM_TIMEOUT_MS = 80;
134
+
135
+ // Drag speed (px/ms) below which a gesture is classified as a deliberate scroll.
136
+ // Computed from position delta and elapsed time in onScrollEndDrag; works on all platforms.
137
+ // ~0.5 px/ms ≈ 300px in 600ms (slow deliberate drag); a fling is typically >1 px/ms.
138
+ const SWIPE_VELOCITY_THRESHOLD = 0.5;
139
+
140
+ // Mirrors Android SDK's direction inference: compare |dx| vs |dy|, sign gives direction.
141
+ // contentOffset delta: dy > 0 = user scrolled DOWN, dx > 0 = user scrolled RIGHT.
142
+ function inferDirectionFromDelta(dx, dy) {
143
+ if (Math.abs(dx) > Math.abs(dy)) {
144
+ return dx > 0 ? 'right' : 'left';
145
+ }
146
+ return dy > 0 ? 'down' : 'up';
147
+ }
148
+
149
+ // Shallow-recursive text extraction from React children.
150
+ // Handles: string, number, arrays, and React elements with props.children.
151
+ function extractInnerText(children) {
152
+ var _children$props;
153
+ if (children === null || children === undefined) return undefined;
154
+ if (typeof children === 'string') return children.trim() || undefined;
155
+ if (typeof children === 'number') return String(children);
156
+ if (Array.isArray(children)) {
157
+ const parts = children.map(extractInnerText).filter(Boolean);
158
+ return parts.length > 0 ? parts.join(' ') : undefined;
159
+ }
160
+ if ((children == null || (_children$props = children.props) == null ? void 0 : _children$props.children) !== undefined) {
161
+ return extractInnerText(children.props.children);
162
+ }
163
+ return undefined;
164
+ }
165
+
166
+ let isEnabled = false;
167
+ // No-op default so reportInteraction never needs a null-check.
168
+ // Set to the real callback by the constructor and cleared on disable().
169
+ let onInteraction = () => {};
170
+
171
+ // Module-level scroll state shared across all ScrollView instances.
172
+ // Hooks cannot be used in our forwardRef wrappers because the plugin's React import
173
+ // and the renderer's React are different instances in a consuming app — hooks rely on
174
+ // ReactCurrentDispatcher which is instance-specific. Non-hook APIs (forwardRef,
175
+ // createElement) work fine because they use Symbol.for() which is global.
176
+ // Concurrent scrolling of two ScrollViews is extremely rare on mobile, so shared
177
+ // state is acceptable here.
178
+ let scrollState = {
179
+ startOffset: null,
180
+ startTime: null,
181
+ timeout: null
182
+ };
183
+ function reportInteraction(ctx) {
184
+ if (!isEnabled) return;
185
+ onInteraction(ctx);
186
+ }
187
+ function makeInstrumentedPressable(OrigComp, componentName) {
188
+ const InstrumentedComp = /*#__PURE__*/React.forwardRef(function InstrumentedPressable(props, ref) {
189
+ const wrappedProps = props.onPress ? _extends({}, props, {
190
+ onPress: e => {
191
+ var _props$accessibilityL, _extractInnerText;
192
+ const {
193
+ pageX,
194
+ pageY
195
+ } = e.nativeEvent;
196
+ reportInteraction({
197
+ type: 'click',
198
+ attributes: {
199
+ x: Math.round(pageX * 100) / 100,
200
+ y: Math.round(pageY * 100) / 100
201
+ },
202
+ element_classes: componentName,
203
+ target_element: (_props$accessibilityL = props.accessibilityLabel) != null ? _props$accessibilityL : componentName,
204
+ target_id: props.testID,
205
+ inner_text: (_extractInnerText = extractInnerText(props.children)) != null ? _extractInnerText : props.title
206
+ });
207
+ props.onPress(e);
208
+ }
209
+ }) : props;
210
+ return /*#__PURE__*/React.createElement(OrigComp, _extends({}, wrappedProps, {
211
+ ref
212
+ }));
213
+ });
214
+ InstrumentedComp.displayName = componentName;
215
+ return InstrumentedComp;
216
+ }
217
+ function makeInstrumentedSwitch(OrigComp) {
218
+ const InstrumentedSwitch = /*#__PURE__*/React.forwardRef(function InstrumentedSwitch(props, ref) {
219
+ const wrappedProps = props.onValueChange ? _extends({}, props, {
220
+ onValueChange: value => {
221
+ var _props$accessibilityL2;
222
+ reportInteraction({
223
+ type: 'click',
224
+ element_classes: 'Switch',
225
+ target_element: (_props$accessibilityL2 = props.accessibilityLabel) != null ? _props$accessibilityL2 : 'Switch',
226
+ target_id: props.testID
227
+ });
228
+ props.onValueChange(value);
229
+ }
230
+ }) : props;
231
+ return /*#__PURE__*/React.createElement(OrigComp, _extends({}, wrappedProps, {
232
+ ref
233
+ }));
234
+ });
235
+ InstrumentedSwitch.displayName = 'Switch';
236
+ return InstrumentedSwitch;
237
+ }
238
+ function makeInstrumentedScrollView(OrigComp) {
239
+ const InstrumentedScrollView = /*#__PURE__*/React.forwardRef(function InstrumentedScrollView(props, ref) {
240
+ const wrappedProps = _extends({}, props, {
241
+ onScrollBeginDrag: e => {
242
+ scrollState.startOffset = _extends({}, e.nativeEvent.contentOffset);
243
+ scrollState.startTime = Date.now();
244
+ props.onScrollBeginDrag == null || props.onScrollBeginDrag(e);
245
+ },
246
+ onScrollEndDrag: e => {
247
+ var _scrollState$timeout;
248
+ const endOffset = _extends({}, e.nativeEvent.contentOffset);
249
+ // Capture start state immediately — scrollState may be mutated by a
250
+ // concurrent ScrollView's onScrollBeginDrag before the timeout fires.
251
+ const {
252
+ startOffset,
253
+ startTime
254
+ } = scrollState;
255
+ clearTimeout((_scrollState$timeout = scrollState.timeout) != null ? _scrollState$timeout : undefined);
256
+
257
+ // Compute drag speed (px/ms) to distinguish a deliberate scroll from a fling.
258
+ // Works on both iOS and Android without relying on native velocity fields.
259
+ let classified = false;
260
+ if (startOffset !== null && startTime !== null) {
261
+ const elapsed = Date.now() - startTime;
262
+ const dx = endOffset.x - startOffset.x;
263
+ const dy = endOffset.y - startOffset.y;
264
+ const speed = elapsed > 0 ? Math.sqrt(dx * dx + dy * dy) / elapsed : 0;
265
+ if (speed < SWIPE_VELOCITY_THRESHOLD) {
266
+ // Slow deliberate drag → report scroll immediately.
267
+ reportInteraction({
268
+ type: 'scroll',
269
+ target_element: 'ScrollView',
270
+ direction: inferDirectionFromDelta(dx, dy)
271
+ });
272
+ scrollState = {
273
+ startOffset: null,
274
+ startTime: null,
275
+ timeout: null
276
+ };
277
+ classified = true;
278
+ }
279
+ }
280
+ if (!classified) {
281
+ // Fast drag → wait for onMomentumScrollBegin to classify as swipe.
282
+ // If momentum doesn't fire within the window, fall back to scroll.
283
+ // Uses captured startOffset (not scrollState.startOffset) to avoid
284
+ // reading stale state if another scroll begins before the timeout fires.
285
+ scrollState.timeout = setTimeout(() => {
286
+ if (startOffset) {
287
+ reportInteraction({
288
+ type: 'scroll',
289
+ target_element: 'ScrollView',
290
+ direction: inferDirectionFromDelta(endOffset.x - startOffset.x, endOffset.y - startOffset.y)
291
+ });
292
+ }
293
+ scrollState = {
294
+ startOffset: null,
295
+ startTime: null,
296
+ timeout: null
297
+ };
298
+ }, SWIPE_MOMENTUM_TIMEOUT_MS);
299
+ }
300
+ props.onScrollEndDrag == null || props.onScrollEndDrag(e);
301
+ },
302
+ onMomentumScrollBegin: e => {
303
+ var _scrollState$timeout2;
304
+ clearTimeout((_scrollState$timeout2 = scrollState.timeout) != null ? _scrollState$timeout2 : undefined);
305
+ const currentOffset = _extends({}, e.nativeEvent.contentOffset);
306
+ const {
307
+ startOffset
308
+ } = scrollState;
309
+ if (startOffset) {
310
+ reportInteraction({
311
+ type: 'swipe',
312
+ target_element: 'ScrollView',
313
+ direction: inferDirectionFromDelta(currentOffset.x - startOffset.x, currentOffset.y - startOffset.y)
314
+ });
315
+ }
316
+ scrollState = {
317
+ startOffset: null,
318
+ startTime: null,
319
+ timeout: null
320
+ };
321
+ props.onMomentumScrollBegin == null || props.onMomentumScrollBegin(e);
322
+ }
323
+ });
324
+ return /*#__PURE__*/React.createElement(OrigComp, _extends({}, wrappedProps, {
325
+ ref
326
+ }));
327
+ });
328
+ InstrumentedScrollView.displayName = 'ScrollView';
329
+
330
+ // Copy static properties from OrigComp (e.g. ScrollView.Context used by VirtualizedList/FlatList).
331
+ // React.forwardRef returns a plain object so these statics are lost without explicit copying.
332
+ const skipKeys = new Set(['$$typeof', 'render', 'displayName']);
333
+ Object.getOwnPropertyNames(OrigComp).forEach(key => {
334
+ if (skipKeys.has(key)) return;
335
+ try {
336
+ const descriptor = Object.getOwnPropertyDescriptor(OrigComp, key);
337
+ if (descriptor) Object.defineProperty(InstrumentedScrollView, key, descriptor);
338
+ } catch (_unused) {/* ignore non-configurable descriptors */}
339
+ });
340
+ return InstrumentedScrollView;
341
+ }
342
+
343
+ // react-native is in Metro's nonInlinedRequires list, so app code reads properties
344
+ // like _rn.Pressable from the shared module object at each render. Mutating the
345
+ // module object here (via Object.defineProperty) means every subsequent property
346
+ // read returns our wrappers.
347
+ //
348
+ // react-native/index.js defines exports as getter-only accessors with no setters,
349
+ // so direct assignment is silently ignored. Object.defineProperty with a data
350
+ // descriptor converts the accessor to a writable data property.
351
+ const RN = require('react-native');
352
+ const originals = {
353
+ Button: RN.Button,
354
+ Pressable: RN.Pressable,
355
+ TouchableOpacity: RN.TouchableOpacity,
356
+ TouchableHighlight: RN.TouchableHighlight,
357
+ TouchableNativeFeedback: RN.TouchableNativeFeedback,
358
+ TouchableWithoutFeedback: RN.TouchableWithoutFeedback,
359
+ Switch: RN.Switch,
360
+ ScrollView: RN.ScrollView
361
+ };
362
+
363
+ // Wrapper components are created once from the originals captured above.
364
+ // They are patched into / restored from the RN module in enable() / disable().
365
+ const instrumented = {
366
+ Button: makeInstrumentedPressable(originals.Button, 'Button'),
367
+ Pressable: makeInstrumentedPressable(originals.Pressable, 'Pressable'),
368
+ TouchableOpacity: makeInstrumentedPressable(originals.TouchableOpacity, 'TouchableOpacity'),
369
+ TouchableHighlight: makeInstrumentedPressable(originals.TouchableHighlight, 'TouchableHighlight'),
370
+ TouchableNativeFeedback: makeInstrumentedPressable(originals.TouchableNativeFeedback, 'TouchableNativeFeedback'),
371
+ TouchableWithoutFeedback: makeInstrumentedPressable(originals.TouchableWithoutFeedback, 'TouchableWithoutFeedback'),
372
+ Switch: makeInstrumentedSwitch(originals.Switch),
373
+ ScrollView: makeInstrumentedScrollView(originals.ScrollView)
374
+ };
375
+ function defineComp(key, value) {
376
+ Object.defineProperty(RN, key, {
377
+ value,
378
+ writable: true,
379
+ configurable: true,
380
+ enumerable: true
381
+ });
382
+ }
383
+ function patchComponents() {
384
+ defineComp('Button', instrumented.Button);
385
+ defineComp('Pressable', instrumented.Pressable);
386
+ defineComp('TouchableOpacity', instrumented.TouchableOpacity);
387
+ defineComp('TouchableHighlight', instrumented.TouchableHighlight);
388
+ defineComp('TouchableNativeFeedback', instrumented.TouchableNativeFeedback);
389
+ defineComp('TouchableWithoutFeedback', instrumented.TouchableWithoutFeedback);
390
+ defineComp('Switch', instrumented.Switch);
391
+ defineComp('ScrollView', instrumented.ScrollView);
392
+ }
393
+ function restoreComponents() {
394
+ defineComp('Button', originals.Button);
395
+ defineComp('Pressable', originals.Pressable);
396
+ defineComp('TouchableOpacity', originals.TouchableOpacity);
397
+ defineComp('TouchableHighlight', originals.TouchableHighlight);
398
+ defineComp('TouchableNativeFeedback', originals.TouchableNativeFeedback);
399
+ defineComp('TouchableWithoutFeedback', originals.TouchableWithoutFeedback);
400
+ defineComp('Switch', originals.Switch);
401
+ defineComp('ScrollView', originals.ScrollView);
402
+ }
403
+ class UserInteractionInstrumentation extends InstrumentationBase {
404
+ constructor(config) {
405
+ super(USER_INTERACTION_INSTRUMENTATION_NAME, USER_INTERACTION_INSTRUMENTATION_VERSION, {});
406
+ onInteraction = config.onInteraction;
407
+ }
408
+
409
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
410
+ init() {}
411
+ enable() {
412
+ isEnabled = true;
413
+ patchComponents();
414
+ }
415
+ disable() {
416
+ isEnabled = false;
417
+ onInteraction = () => {};
418
+ restoreComponents();
419
+ if (scrollState.timeout) {
420
+ clearTimeout(scrollState.timeout);
421
+ }
422
+ scrollState = {
423
+ startOffset: null,
424
+ startTime: null,
425
+ timeout: null
426
+ };
427
+ }
428
+ }
429
+
127
430
  const DEFAULT_SAMPLE_DURATION_MS = 5000;
128
431
  const DEFAULT_SAMPLE_INTERVAL_MS = 60000;
129
432
  let isSampling = false;
@@ -218,7 +521,7 @@ function stopJsRefreshRateSampler() {
218
521
  appStateSub = null;
219
522
  }
220
523
 
221
- var version = "0.2.10";
524
+ var version = "0.3.0";
222
525
  var pkg = {
223
526
  version: version};
224
527
 
@@ -317,6 +620,52 @@ let OtelNetworkAttrs = /*#__PURE__*/function (OtelNetworkAttrs) {
317
620
  return OtelNetworkAttrs;
318
621
  }({});
319
622
 
623
+ /**
624
+ * Returns the first rule whose `urlPattern` regex or exact `url` matches the
625
+ * given URL. Returns undefined if no rule matches or the list is empty.
626
+ */
627
+ function resolveNetworkCaptureRule(url, rules) {
628
+ return rules.find(rule => rule.urlPattern ? rule.urlPattern.test(url) : rule.url === url);
629
+ }
630
+
631
+ /**
632
+ * Filters a headers map to only the keys present in `allowlist`.
633
+ * Matching is case-insensitive; output keys use the casing from `allowlist`.
634
+ */
635
+ function filterHeaders(headers, allowlist) {
636
+ const result = {};
637
+ for (const [key, value] of Object.entries(headers)) {
638
+ const configKey = allowlist.find(k => k.toLowerCase() === key.toLowerCase());
639
+ if (configKey !== undefined) result[configKey] = value;
640
+ }
641
+ return result;
642
+ }
643
+
644
+ /**
645
+ * Returns `body` if it is within `maxChars`, otherwise returns `undefined`.
646
+ * Bodies over the limit are dropped entirely — never truncated.
647
+ */
648
+ function applyPayloadLimit(body, maxChars = 1024) {
649
+ return body.length <= maxChars ? body : undefined;
650
+ }
651
+
652
+ /**
653
+ * Converts NetworkCaptureRule[] to a form that can cross the JS→native bridge.
654
+ * RegExp cannot be serialized directly, so `urlPattern` is split into its
655
+ * `source` and `flags` strings; the native side reconstructs the regex.
656
+ */
657
+ function serializeNetworkCaptureRules(rules) {
658
+ return rules.map(rule => {
659
+ var _rule$urlPattern, _rule$urlPattern2;
660
+ return _extends({}, rule, {
661
+ urlPattern: (_rule$urlPattern = rule.urlPattern) == null ? void 0 : _rule$urlPattern.source,
662
+ // Strip 'g' and 'y' flags — stateful flags cause RegExp.lastIndex side-effects
663
+ // when the native side calls .test() repeatedly on a shared instance.
664
+ urlPatternFlags: (_rule$urlPattern2 = rule.urlPattern) == null ? void 0 : _rule$urlPattern2.flags.replace(/[gy]/g, '')
665
+ });
666
+ });
667
+ }
668
+
320
669
  class CoralogixFetchInstrumentation extends FetchInstrumentation {
321
670
  constructor(config) {
322
671
  var _traceParentInHeader$;
@@ -330,7 +679,9 @@ class CoralogixFetchInstrumentation extends FetchInstrumentation {
330
679
  // If enabled and user didn't provide urls -> match all
331
680
  propagateTraceHeaderCorsUrls: enabled ? urls != null ? urls : /.*/ : allowNone()
332
681
  };
333
- fetchConfig.applyCustomAttributesOnSpan = (span, request, result) => {
682
+ fetchConfig.applyCustomAttributesOnSpan = async function (span, request, result) {
683
+ // span.end() must remain synchronous and first — this completes the OTel span
684
+ // regardless of any async work that follows for capture enrichment.
334
685
  span.end();
335
686
  const readableSpan = span;
336
687
  const attrs = readableSpan.attributes;
@@ -362,6 +713,66 @@ class CoralogixFetchInstrumentation extends FetchInstrumentation {
362
713
  customTraceId,
363
714
  customSpanId
364
715
  };
716
+ const networkExtraConfig = config.networkExtraConfig;
717
+ if (networkExtraConfig != null && networkExtraConfig.length) {
718
+ const rule = resolveNetworkCaptureRule(url, networkExtraConfig);
719
+ if (rule) {
720
+ var _rule$reqHeaders, _rule$resHeaders;
721
+ // Request headers
722
+ if ((_rule$reqHeaders = rule.reqHeaders) != null && _rule$reqHeaders.length) {
723
+ let rawHeaders;
724
+ if (request instanceof Request) {
725
+ rawHeaders = headersToRecord(request.headers);
726
+ } else {
727
+ const h = request.headers;
728
+ if (h instanceof Headers) {
729
+ rawHeaders = headersToRecord(h);
730
+ } else if (Array.isArray(h)) {
731
+ rawHeaders = Object.fromEntries(h);
732
+ } else {
733
+ var _ref;
734
+ rawHeaders = (_ref = h) != null ? _ref : {};
735
+ }
736
+ }
737
+ const filtered = filterHeaders(rawHeaders, rule.reqHeaders);
738
+ if (Object.keys(filtered).length > 0) details.request_headers = filtered;
739
+ }
740
+
741
+ // Response headers
742
+ if ((_rule$resHeaders = rule.resHeaders) != null && _rule$resHeaders.length && result instanceof Response) {
743
+ const filtered = filterHeaders(headersToRecord(result.headers), rule.resHeaders);
744
+ if (Object.keys(filtered).length > 0) details.response_headers = filtered;
745
+ }
746
+
747
+ // Request payload — string bodies only (FormData/Blob/ReadableStream skipped)
748
+ if (rule.collectReqPayload) {
749
+ let reqBody;
750
+ if (request instanceof Request) {
751
+ try {
752
+ reqBody = await request.clone().text();
753
+ } catch (_unused) {/* consumed or unavailable */}
754
+ } else {
755
+ const body = request == null ? void 0 : request.body;
756
+ if (typeof body === 'string') reqBody = body;
757
+ }
758
+ if (reqBody !== undefined) {
759
+ const limited = applyPayloadLimit(reqBody);
760
+ if (limited !== undefined) details.request_payload = limited;
761
+ }
762
+ }
763
+
764
+ // Response payload — clone before body is consumed by app code (best-effort)
765
+ if (rule.collectResPayload && result instanceof Response && !result.bodyUsed) {
766
+ try {
767
+ const text = await result.clone().text();
768
+ const limited = applyPayloadLimit(text);
769
+ if (limited !== undefined) details.response_payload = limited;
770
+ } catch (_unused2) {/* consumed or unavailable */}
771
+ }
772
+ }
773
+ }
774
+
775
+ // reportNetworkRequest must always be last, after all awaits
365
776
  CoralogixRum.reportNetworkRequest(details);
366
777
  };
367
778
  super(fetchConfig);
@@ -374,6 +785,13 @@ function allowNone() {
374
785
  // matches nothing
375
786
  return /^$/;
376
787
  }
788
+ function headersToRecord(headers) {
789
+ const result = {};
790
+ headers.forEach((value, key) => {
791
+ result[key] = value;
792
+ });
793
+ return result;
794
+ }
377
795
 
378
796
  function isSessionReplayOptionsValid(options) {
379
797
  const scaleValid = options.captureScale > 0 && options.captureScale <= 1;
@@ -467,7 +885,8 @@ const CoralogixRum = {
467
885
  finalOptions = resolvedOptions;
468
886
  }
469
887
  await CxSdk.initialize(_extends({}, finalOptions, {
470
- frameworkVersion: pkg.version
888
+ frameworkVersion: pkg.version,
889
+ networkExtraConfig: finalOptions.networkExtraConfig ? serializeNetworkCaptureRules(finalOptions.networkExtraConfig) : undefined
471
890
  }));
472
891
  isInited = true;
473
892
  },
@@ -594,6 +1013,10 @@ const CoralogixRum = {
594
1013
  const SessionReplay = {
595
1014
  init: async options => {
596
1015
  logger.debug("session replay: init called with options: ", options);
1016
+ if (!CoralogixRum.isInited) {
1017
+ logger.warn("SessionReplay.init called before CoralogixRum is initialized. Call and await CoralogixRum.init() first to avoid initialization errors.");
1018
+ return false;
1019
+ }
597
1020
  const optionsValid = isSessionReplayOptionsValid(options);
598
1021
  if (!optionsValid) {
599
1022
  logger.warn("invalid options in SessionReplay.init: ", options);
@@ -707,6 +1130,20 @@ async function registerCoralogixInstrumentations(options) {
707
1130
  trackMobileVitals(options);
708
1131
  }
709
1132
 
1133
+ // User interaction instrumentation (clicks, scrolls, swipes)
1134
+ const shouldInterceptUserInteractions = !instrumentationsOptions || instrumentationsOptions.user_interaction !== false;
1135
+ if (shouldInterceptUserInteractions) {
1136
+ instrumentations.push(new UserInteractionInstrumentation({
1137
+ onInteraction: ctx => {
1138
+ // isInited becomes true only after CxSdk.initialize() resolves,
1139
+ // so this guard drops any interactions that fire in the brief window
1140
+ // between instrumentation registration and native SDK init completing.
1141
+ if (!isInited) return;
1142
+ CxSdk.reportUserInteraction(ctx);
1143
+ }
1144
+ }));
1145
+ }
1146
+
710
1147
  // Register Instrumentations
711
1148
  _deregisterInstrumentations = registerInstrumentations({
712
1149
  tracerProvider,
package/ios/CxSdk.mm CHANGED
@@ -41,6 +41,8 @@ RCT_EXTERN_METHOD(setApplicationContext:(NSDictionary *)applicationContext
41
41
  RCT_EXTERN_METHOD(getSessionId:(RCTPromiseResolveBlock)resolve
42
42
  withRejecter:(RCTPromiseRejectBlock)reject)
43
43
 
44
+ RCT_EXTERN_METHOD(reportUserInteraction:(NSDictionary *)interaction)
45
+
44
46
  RCT_EXTERN_METHOD(shutdown:(RCTPromiseResolveBlock)resolve
45
47
  withRejecter:(RCTPromiseRejectBlock)reject)
46
48