@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/CHANGELOG.md +27 -0
- package/CxSdk.podspec +3 -3
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/cxsdk/CxSdkModule.kt +66 -2
- package/index.cjs.js +441 -4
- package/index.esm.js +441 -4
- package/ios/CxSdk.mm +2 -0
- package/ios/CxSdk.swift +80 -9
- package/package.json +1 -1
- package/src/index.d.ts +3 -2
- package/src/instrumentations/network/networkCaptureUtils.d.ts +22 -0
- package/src/instrumentations/user-interaction/UserInteractionInstrumentation.d.ts +10 -0
- package/src/instrumentations/user-interaction/consts.d.ts +4 -0
- package/src/instrumentations/user-interaction/gestureUtils.d.ts +3 -0
- package/src/instrumentations/user-interaction/index.d.ts +1 -0
- package/src/model/CoralogixOtelWebType.d.ts +2 -1
- package/src/model/NetworkRequestDetails.d.ts +4 -0
- package/src/model/Types.d.ts +30 -1
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
|
|
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
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 @@
|
|
|
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
|
};
|
package/src/model/Types.d.ts
CHANGED
|
@@ -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 {};
|