@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/ios/CxSdk.swift CHANGED
@@ -163,11 +163,38 @@ class CxSdk: RCTEventEmitter {
163
163
  reject("Invalid networkRequestDictionary", "networkRequestDictionary is not a dictionary", nil)
164
164
  return
165
165
  }
166
+
166
167
  let cxNetworkDict = toCoralogixNetwork(dictionary: dictionary)
168
+ print("[CX DEBUG] cxNetworkDict response_payload: \( cxNetworkDict)")
167
169
  coralogixRum?.setNetworkRequestContext(dictionary: cxNetworkDict)
168
170
  resolve("reportNetworkRequest success")
169
171
  }
170
172
 
173
+ @objc(reportUserInteraction:)
174
+ func reportUserInteraction(interaction: NSDictionary) {
175
+ var dictionary: [String: Any] = [:]
176
+
177
+ if let type = interaction["type"] as? String { dictionary["event_name"] = type }
178
+ if let targetElement = interaction["target_element"] as? String { dictionary["target_element"] = targetElement }
179
+ if let elementClasses = interaction["element_classes"] as? String { dictionary["element_classes"] = elementClasses }
180
+ if let targetId = interaction["target_id"] as? String { dictionary["element_id"] = targetId }
181
+ if let innerText = interaction["inner_text"] as? String { dictionary["target_element_inner_text"] = innerText }
182
+ if let direction = interaction["direction"] as? String { dictionary["scroll_direction"] = direction }
183
+ if let attributes = interaction["attributes"] as? [String: Any] {
184
+ if let x = attributes["x"] as? Double { dictionary["x"] = x }
185
+ if let y = attributes["y"] as? Double { dictionary["y"] = y }
186
+ }
187
+
188
+ let payload = dictionary
189
+ DispatchQueue.main.async { [weak self] in
190
+ guard let rum = self?.coralogixRum else {
191
+ print("[CxSdk] reportUserInteraction called before CoralogixRum is initialized, dropping interaction")
192
+ return
193
+ }
194
+ rum.setUserInteraction(payload)
195
+ }
196
+ }
197
+
171
198
  @objc(shutdown:withRejecter:)
172
199
  func shutdown(resolve:RCTPromiseResolveBlock,
173
200
  reject:RCTPromiseRejectBlock) -> Void {
@@ -187,7 +214,7 @@ class CxSdk: RCTEventEmitter {
187
214
  let message = dictionary[Keys.errorMessage.rawValue] as? String ?? ""
188
215
  let errorType = dictionary[Keys.errorType.rawValue] as? String ?? "5"
189
216
  let isCrash = dictionary[Keys.isCrash.rawValue] as? Bool ?? false
190
-
217
+
191
218
  coralogixRum?.reportError(message: message,
192
219
  stackTrace: stackTrace,
193
220
  errorType: errorType,
@@ -229,15 +256,19 @@ class CxSdk: RCTEventEmitter {
229
256
  func initializeSessionReplay(options: NSDictionary,
230
257
  resolve:RCTPromiseResolveBlock,
231
258
  reject:RCTPromiseRejectBlock) -> Void {
259
+ guard coralogixRum != nil else {
260
+ reject("CX_SDK_ERROR", "Coralogix RUM must be initialized before Session Replay. Call CoralogixRum.init() first and await it.", nil)
261
+ return
262
+ }
232
263
  do {
233
264
  let sessionReplayOptions = try self.toSessionReplayOptions(parameter: options)
234
265
  SessionReplay.initializeWithOptions(sessionReplayOptions:sessionReplayOptions)
266
+ resolve("initializeSessionReplay success")
235
267
  } catch let error as CxSdkError {
236
268
  reject("CX_SDK_ERROR", error.localizedDescription, error)
237
269
  } catch {
238
270
  reject("UNEXPECTED_ERROR", "An unexpected error occurred: \(error.localizedDescription)", error)
239
271
  }
240
- resolve("initializeSessionReplay success")
241
272
  }
242
273
 
243
274
  @objc(shutdownSessionReplay:withRejecter:)
@@ -288,7 +319,7 @@ class CxSdk: RCTEventEmitter {
288
319
  }
289
320
  resolve("captureScreenshot success")
290
321
  }
291
-
322
+
292
323
  @objc(maskViewByTag:withResolver:withRejecter:)
293
324
  func maskViewByTag(viewTag: NSNumber,
294
325
  resolve:@escaping RCTPromiseResolveBlock,
@@ -300,7 +331,7 @@ class CxSdk: RCTEventEmitter {
300
331
  reject("MASK_VIEW_ERROR", "no ui manager found, aborting maskViewByTag", nil)
301
332
  return
302
333
  }
303
-
334
+
304
335
  RCTExecuteOnUIManagerQueue {
305
336
  uiManager.addUIBlock { (_, viewRegistry) in
306
337
  if let view = viewRegistry?[viewTag] as? UIView {
@@ -336,6 +367,38 @@ class CxSdk: RCTEventEmitter {
336
367
  }
337
368
  }
338
369
 
370
+ private func toNetworkCaptureRuleList(_ rules: [[String: Any]]?) -> [NetworkCaptureRule] {
371
+ guard let rules = rules else { return [] }
372
+ let result = rules.compactMap { dict -> NetworkCaptureRule? in
373
+ let reqHeaders = dict["reqHeaders"] as? [String]
374
+ let resHeaders = dict["resHeaders"] as? [String]
375
+ let collectReqPayload = dict["collectReqPayload"] as? Bool ?? false
376
+ let collectResPayload = dict["collectResPayload"] as? Bool ?? false
377
+ if let source = dict["urlPattern"] as? String {
378
+ let flags = dict["urlPatternFlags"] as? String ?? ""
379
+ var options: NSRegularExpression.Options = []
380
+ if flags.contains("i") { options.insert(.caseInsensitive) }
381
+ if flags.contains("m") { options.insert(.anchorsMatchLines) }
382
+ guard let regex = try? NSRegularExpression(pattern: source, options: options) else {
383
+ return nil
384
+ }
385
+ return NetworkCaptureRule(urlPattern: regex,
386
+ reqHeaders: reqHeaders,
387
+ resHeaders: resHeaders,
388
+ collectReqPayload: collectReqPayload,
389
+ collectResPayload: collectResPayload)
390
+ } else if let url = dict["url"] as? String {
391
+ return NetworkCaptureRule(url: url,
392
+ reqHeaders: reqHeaders,
393
+ resHeaders: resHeaders,
394
+ collectReqPayload: collectReqPayload,
395
+ collectResPayload: collectResPayload)
396
+ }
397
+ return nil
398
+ }
399
+ return result
400
+ }
401
+
339
402
  private func toCoralogixNetwork(dictionary: [String: Any]) -> [String: Any] {
340
403
  var result = [String: Any]()
341
404
  result["url"] = dictionary["url"] as? String ?? ""
@@ -347,6 +410,10 @@ class CxSdk: RCTEventEmitter {
347
410
  result["schema"] = dictionary["schema"] as? String ?? ""
348
411
  result["customTraceId"] = dictionary["customTraceId"] as? String ?? ""
349
412
  result["customSpanId"] = dictionary["customSpanId"] as? String ?? ""
413
+ if let v = dictionary["request_headers"] as? [String: String] { result["request_headers"] = v }
414
+ if let v = dictionary["response_headers"] as? [String: String] { result["response_headers"] = v }
415
+ if let v = dictionary["request_payload"] as? String { result["request_payload"] = v }
416
+ if let v = dictionary["response_payload"] as? String { result["response_payload"] = v }
350
417
  return result
351
418
  }
352
419
 
@@ -391,7 +458,11 @@ class CxSdk: RCTEventEmitter {
391
458
  userMetadata: userMetadata)
392
459
  let ignoreUrls = (parameter["ignoreUrls"] as? [Any])?.compactMap { $0 as? String } ?? []
393
460
  let ignoreError = (parameter["ignoreErrors"] as? [Any])?.compactMap { $0 as? String } ?? []
394
- let options = CoralogixExporterOptions(coralogixDomain: coralogixDomain,
461
+ let rawNetworkExtraConfig = parameter["networkExtraConfig"] as? [[String: Any]]
462
+ let networkExtraConfig = toNetworkCaptureRuleList(rawNetworkExtraConfig)
463
+
464
+ let options = CoralogixExporterOptions(
465
+ coralogixDomain: coralogixDomain,
395
466
  userContext: coralogixUser,
396
467
  environment: parameter["environment"] as? String ?? "",
397
468
  application: application,
@@ -400,12 +471,12 @@ class CxSdk: RCTEventEmitter {
400
471
  ignoreUrls: ignoreUrls,
401
472
  ignoreErrors: ignoreError,
402
473
  labels: labels,
403
- fpsSampleRate: parameter["fpsSamplingSeconds"] as? TimeInterval ?? 300,
404
474
  instrumentations: instrumentationDict,
405
475
  collectIPData: parameter["collectIPData"] as? Bool ?? true,
406
476
  proxyUrl: parameter["proxyUrl"] as? String ?? nil,
407
477
  traceParentInHeader: mapTraceParentInHeader(parameter["traceParentInHeader"] as? [String: Any]),
408
478
  mobileVitals: mobileVitalsDict,
479
+ networkExtraConfig: networkExtraConfig,
409
480
  debug: parameter["debug"] as? Bool ?? true)
410
481
 
411
482
  return options
@@ -449,9 +520,9 @@ class CxSdk: RCTEventEmitter {
449
520
 
450
521
  private func mapTraceParentInHeader(_ rnConfig: [String: Any]?) -> [String: Any]? {
451
522
  guard let rnConfig = rnConfig else { return nil }
452
-
523
+
453
524
  var nativeConfig: [String: Any] = [:]
454
-
525
+
455
526
  // Copy all keys, but rename "enabled" to "enable"
456
527
  for (key, value) in rnConfig {
457
528
  if key == "enabled" {
@@ -460,7 +531,7 @@ class CxSdk: RCTEventEmitter {
460
531
  nativeConfig[key] = value
461
532
  }
462
533
  }
463
-
534
+
464
535
  return nativeConfig.isEmpty ? nil : nativeConfig
465
536
  }
466
537
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coralogix/react-native-plugin",
3
- "version": "0.2.10",
3
+ "version": "0.3.0",
4
4
  "description": "Official Coralogix React Native plugin",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Coralogix",
package/src/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { UserContextConfig } from './model/UserContextConfig';
2
2
  import type { ApplicationContextConfig } from './model/ApplicationContextConfig';
3
3
  import type { CoralogixOtelWebType } from './model/CoralogixOtelWebType';
4
- import type { CoralogixBrowserSdkConfig, CoralogixStackFrame, CxSpan, HybridMetric } from './model/Types';
4
+ import type { CoralogixBrowserSdkConfig, CoralogixStackFrame, CxSpan, HybridMetric, InteractionContext } from './model/Types';
5
5
  import { CoralogixLogSeverity } from './model/Types';
6
6
  import type { NetworkRequestDetails } from './model/NetworkRequestDetails';
7
7
  import type { CustomMeasurement } from './model/CustomMeasurement';
@@ -36,6 +36,7 @@ interface CxSdkClient {
36
36
  stopSessionRecording(): void;
37
37
  captureScreenshot(): void;
38
38
  maskViewByTag(viewTag: number): void;
39
+ reportUserInteraction(interaction: InteractionContext): void;
39
40
  }
40
41
  export declare const CxSdk: CxSdkClient;
41
42
  export declare const CoralogixRum: CoralogixOtelWebType;
@@ -45,7 +46,7 @@ export { type ApplicationContextConfig } from './model/ApplicationContextConfig'
45
46
  export { type ViewContextConfig } from './model/ViewContextConfig';
46
47
  export { CoralogixDomain } from './model/CoralogixDomain';
47
48
  export { type UserContextConfig } from './model/UserContextConfig';
48
- export { type CoralogixBrowserSdkConfig, CoralogixLogSeverity, } from './model/Types';
49
+ export { type CoralogixBrowserSdkConfig, type NetworkCaptureRule, CoralogixLogSeverity, } from './model/Types';
49
50
  export { type CoralogixOtelWebOptionsInstrumentations } from './model/CoralogixOtelWebOptionsInstrumentations';
50
51
  export { type CustomMeasurement } from './model/CustomMeasurement';
51
52
  export { type NetworkRequestDetails } from './model/NetworkRequestDetails';
@@ -0,0 +1,22 @@
1
+ import { NetworkCaptureRule } from '../../model/Types';
2
+ /**
3
+ * Returns the first rule whose `urlPattern` regex or exact `url` matches the
4
+ * given URL. Returns undefined if no rule matches or the list is empty.
5
+ */
6
+ export declare function resolveNetworkCaptureRule(url: string, rules: NetworkCaptureRule[]): NetworkCaptureRule | undefined;
7
+ /**
8
+ * Filters a headers map to only the keys present in `allowlist`.
9
+ * Matching is case-insensitive; output keys use the casing from `allowlist`.
10
+ */
11
+ export declare function filterHeaders(headers: Record<string, string>, allowlist: string[]): Record<string, string>;
12
+ /**
13
+ * Returns `body` if it is within `maxChars`, otherwise returns `undefined`.
14
+ * Bodies over the limit are dropped entirely — never truncated.
15
+ */
16
+ export declare function applyPayloadLimit(body: string, maxChars?: number): string | undefined;
17
+ /**
18
+ * Converts NetworkCaptureRule[] to a form that can cross the JS→native bridge.
19
+ * RegExp cannot be serialized directly, so `urlPattern` is split into its
20
+ * `source` and `flags` strings; the native side reconstructs the regex.
21
+ */
22
+ export declare function serializeNetworkCaptureRules(rules: NetworkCaptureRule[]): any[];
@@ -0,0 +1,10 @@
1
+ import { InstrumentationBase } from '@opentelemetry/instrumentation';
2
+ import type { InteractionContext } from '../../model/Types';
3
+ export declare class UserInteractionInstrumentation extends InstrumentationBase {
4
+ constructor(config: {
5
+ onInteraction: (ctx: InteractionContext) => void;
6
+ });
7
+ init(): void;
8
+ enable(): void;
9
+ disable(): void;
10
+ }
@@ -0,0 +1,4 @@
1
+ export declare const USER_INTERACTION_INSTRUMENTATION_NAME = "user-interaction";
2
+ export declare const USER_INTERACTION_INSTRUMENTATION_VERSION = "1";
3
+ export declare const SWIPE_MOMENTUM_TIMEOUT_MS = 80;
4
+ export declare const SWIPE_VELOCITY_THRESHOLD = 0.5;
@@ -0,0 +1,3 @@
1
+ export type Direction = 'up' | 'down' | 'left' | 'right';
2
+ export declare function inferDirectionFromDelta(dx: number, dy: number): Direction;
3
+ export declare function extractInnerText(children: any): string | undefined;
@@ -0,0 +1 @@
1
+ export { UserInteractionInstrumentation } from './UserInteractionInstrumentation';
@@ -9,8 +9,9 @@ import { NetworkRequestDetails } from './NetworkRequestDetails';
9
9
  export interface CoralogixOtelWebType extends SendLog {
10
10
  /**
11
11
  * Init CoralogixRum.
12
+ * Returns a Promise that resolves when initialization is complete.
12
13
  */
13
- init: (options: CoralogixBrowserSdkConfig) => void;
14
+ init: (options: CoralogixBrowserSdkConfig) => Promise<void>;
14
15
  /**
15
16
  * Turn CoralogixRum off.
16
17
  */
@@ -11,4 +11,8 @@ export type NetworkRequestDetails = {
11
11
  errorMessage?: string | null;
12
12
  customTraceId: string;
13
13
  customSpanId: string;
14
+ request_headers?: Record<string, string>;
15
+ response_headers?: Record<string, string>;
16
+ request_payload?: string;
17
+ response_payload?: string;
14
18
  };
@@ -133,10 +133,24 @@ export interface CxRumEvent {
133
133
  timestamp: number;
134
134
  isSnapshotEvent?: boolean;
135
135
  }
136
- export interface EditableCxRumEvent extends Omit<CxRumEvent, 'session_context' | 'timestamp'> {
136
+ export interface EditableCxRumEvent extends Omit<CxRumEvent, 'session_context' | 'timestamp' | 'snapshot_context'> {
137
137
  session_context: Pick<SessionContext, keyof UserMetadata>;
138
138
  }
139
139
  export type BeforeSendResult = EditableCxRumEvent | null;
140
+ export interface NetworkCaptureRule {
141
+ /** Exact-string URL match. One of `url` or `urlPattern` is required. */
142
+ url?: string;
143
+ /** Regex URL match. One of `url` or `urlPattern` is required. */
144
+ urlPattern?: RegExp;
145
+ /** Allowlisted request header names to capture (case-insensitive). */
146
+ reqHeaders?: string[];
147
+ /** Allowlisted response header names to capture (case-insensitive). */
148
+ resHeaders?: string[];
149
+ /** Capture request body (string bodies only, ≤1024 chars). Default: false. */
150
+ collectReqPayload?: boolean;
151
+ /** Capture response body (best-effort, ≤1024 chars). Default: false. */
152
+ collectResPayload?: boolean;
153
+ }
140
154
  export interface TraceParentInHeader {
141
155
  enabled: boolean;
142
156
  options?: {
@@ -192,6 +206,8 @@ export interface CoralogixBrowserSdkConfig {
192
206
  beforeSend?: (event: EditableCxRumEvent) => BeforeSendResult;
193
207
  /** Send requests through a proxy */
194
208
  proxyUrl?: string;
209
+ /** Rules for capturing request/response headers and payloads per URL. */
210
+ networkExtraConfig?: NetworkCaptureRule[];
195
211
  /**
196
212
  * JS refresh rate metric collection configuration.
197
213
  */
@@ -208,4 +224,17 @@ export type HybridMetric = {
208
224
  value: number;
209
225
  units: string;
210
226
  };
227
+ export interface InteractionContext {
228
+ type: 'click' | 'scroll' | 'swipe';
229
+ /** Required. Element identifier: accessibilityLabel ?? componentName for clicks; 'ScrollView' for scroll/swipe. */
230
+ target_element: string;
231
+ direction?: 'up' | 'down' | 'left' | 'right';
232
+ element_classes?: string;
233
+ target_id?: string;
234
+ inner_text?: string;
235
+ attributes?: {
236
+ x?: number;
237
+ y?: number;
238
+ };
239
+ }
211
240
  export {};