clintplay-react-native 0.2.1 → 0.2.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/README.md +2 -2
- package/android/build.gradle +13 -16
- package/android/src/main/java/digital/clint/clintplay/ClintplayRNModule.kt +9 -5
- package/dist/index.d.mts +31 -3
- package/dist/index.d.ts +31 -3
- package/dist/index.js +218 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +210 -3
- package/dist/index.mjs.map +1 -1
- package/ios/ClintplayRN.mm +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Session capture SDK for React Native / Expo apps — companion to [`clintplay-sdk`](../sdk) (web). Captures lifecycle, screen dimensions, navigation, JS errors, network requests (Fetch & XHR/Axios), console output, touches (screen-relative & target-relative), and native visual wireframe replay on iOS and Android.
|
|
4
4
|
|
|
5
|
-
> **Status:** `0.2.
|
|
5
|
+
> **Status:** `0.2.2` — Production-ready JS runtime and Native TurboModule. Supports native view hierarchy traversal on iOS (`ios/ClintplayRN.mm`) and Android (`android/.../ClintplayRNModule.kt`), visual wireframe session replay, network interception for `fetch` and `XMLHttpRequest`/Axios, automatic navigation tracking, and intelligent auto-masking for sensitive fields.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -12,7 +12,7 @@ pnpm add clintplay-react-native
|
|
|
12
12
|
npm install clintplay-react-native
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
Peer deps: `react >=18`, `react-native >=0.74`. Optional peer: `@react-native-async-storage/async-storage` (enables the offline retry queue when present).
|
|
15
|
+
Peer deps: `react >=18`, `react-native >=0.74`. Optional peer: `@react-native-async-storage/async-storage` (enables the offline retry queue when present). Android verified on Expo SDK 57 / RN 0.86.3 with New Architecture (debug and release builds, and wireframe capture on an emulator); iOS not re-verified on that version.
|
|
16
16
|
|
|
17
17
|
In your `app.json`, register the config plugin:
|
|
18
18
|
|
package/android/build.gradle
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
buildscript
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
1
|
+
// No `buildscript` block: this module is only built inside a host app, whose
|
|
2
|
+
// root build.gradle already puts the Android and Kotlin Gradle plugins on the
|
|
3
|
+
// classpath (bare RN and Expo templates alike). Don't read a Kotlin version from
|
|
4
|
+
// `rootProject.ext` either: Expo's template defines none, and in Groovy reading
|
|
5
|
+
// a missing `ext` property throws instead of returning null.
|
|
6
|
+
|
|
7
|
+
// On the classpath through the host's root build.gradle (resolved by the
|
|
8
|
+
// includeBuild in its settings.gradle). Runs the codegen for the
|
|
9
|
+
// `codegenConfig` in package.json (`ClintplayRNSpec`): autolinking adds that
|
|
10
|
+
// spec's `codegen/jni` folder to the app's CMake build and its C++
|
|
11
|
+
// ModuleProvider is what binds this TurboModule to JS, so without the plugin
|
|
12
|
+
// the app's CMake configure fails on a directory that was never generated.
|
|
13
|
+
apply plugin: 'com.facebook.react'
|
|
17
14
|
apply plugin: 'com.android.library'
|
|
18
15
|
apply plugin: 'org.jetbrains.kotlin.android'
|
|
19
16
|
|
|
@@ -43,9 +43,11 @@ import kotlin.math.round
|
|
|
43
43
|
// coordinate conversion, background colors, extracted text, and
|
|
44
44
|
// privacy auto-masking (clintplay-mask tags, password fields, and sensitive autofill hints).
|
|
45
45
|
//
|
|
46
|
-
// React Native's codegen emits `NativeClintplayRNSpec`
|
|
47
|
-
// `src/spec/NativeClintplayRN.ts` at build time.
|
|
48
|
-
//
|
|
46
|
+
// React Native's codegen emits `NativeClintplayRNSpec` and its C++ JSI glue
|
|
47
|
+
// from `src/spec/NativeClintplayRN.ts` at build time. This class does not
|
|
48
|
+
// extend that spec: the glue calls the methods below by name and JNI signature
|
|
49
|
+
// (`()Ljava/lang/String;`), so they must match the spec by hand. The
|
|
50
|
+
// TurboReactPackage in `ClintplayRNPackage.kt` routes calls here.
|
|
49
51
|
@ReactModule(name = ClintplayRNModule.NAME)
|
|
50
52
|
class ClintplayRNModule(reactContext: ReactApplicationContext) :
|
|
51
53
|
ReactContextBaseJavaModule(reactContext), TurboModule {
|
|
@@ -326,7 +328,9 @@ class ClintplayRNModule(reactContext: ReactApplicationContext) :
|
|
|
326
328
|
|
|
327
329
|
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
328
330
|
fun captureViewHierarchy(): String {
|
|
329
|
-
|
|
331
|
+
// Not the inherited `currentActivity`: since RN 0.80 that is a deprecated
|
|
332
|
+
// protected Kotlin function, which property syntax does not resolve.
|
|
333
|
+
val activity = reactApplicationContext.currentActivity ?: return FALLBACK_JSON
|
|
330
334
|
|
|
331
335
|
if (activity.isFinishing || activity.isDestroyed) {
|
|
332
336
|
return FALLBACK_JSON
|
|
@@ -376,7 +380,7 @@ class ClintplayRNModule(reactContext: ReactApplicationContext) :
|
|
|
376
380
|
|
|
377
381
|
companion object {
|
|
378
382
|
const val NAME = "ClintplayRN"
|
|
379
|
-
private const val VERSION = "0.2.
|
|
383
|
+
private const val VERSION = "0.2.2"
|
|
380
384
|
private const val FALLBACK_JSON = "{\"version\":1,\"nodes\":[]}"
|
|
381
385
|
|
|
382
386
|
private val NO_METHOD_SENTINEL = Any()
|
package/dist/index.d.mts
CHANGED
|
@@ -7,6 +7,8 @@ interface SessionState {
|
|
|
7
7
|
startedAt: number;
|
|
8
8
|
lastActivity: number;
|
|
9
9
|
userId: string | null;
|
|
10
|
+
tags: Set<string>;
|
|
11
|
+
metadata: Record<string, unknown>;
|
|
10
12
|
}
|
|
11
13
|
declare class SessionManager {
|
|
12
14
|
private state;
|
|
@@ -15,13 +17,16 @@ declare class SessionManager {
|
|
|
15
17
|
private create;
|
|
16
18
|
current(): SessionState;
|
|
17
19
|
touch(): void;
|
|
20
|
+
addTag(tag: string): boolean;
|
|
21
|
+
hasTag(tag: string): boolean;
|
|
22
|
+
setMetadata(data: Record<string, unknown>): void;
|
|
18
23
|
setUser(userId: string | null): boolean;
|
|
19
24
|
isStale(): boolean;
|
|
20
25
|
rotateIfStale(): boolean;
|
|
21
26
|
reset(): void;
|
|
22
27
|
}
|
|
23
28
|
|
|
24
|
-
type EventType = 'meta' | 'screen' | 'lifecycle' | 'navigation' | 'touch' | 'error' | 'log' | 'identify' | 'network' | 'wireframe';
|
|
29
|
+
type EventType = 'meta' | 'screen' | 'lifecycle' | 'navigation' | 'touch' | 'error' | 'log' | 'identify' | 'network' | 'wireframe' | 'metadata' | 'tag';
|
|
25
30
|
interface DeviceInfo {
|
|
26
31
|
platform: 'ios' | 'android' | 'web';
|
|
27
32
|
osVersion?: string;
|
|
@@ -92,6 +97,18 @@ interface NetworkEvent {
|
|
|
92
97
|
durationMs?: number;
|
|
93
98
|
error?: string;
|
|
94
99
|
}
|
|
100
|
+
interface MetadataEvent {
|
|
101
|
+
type: 'metadata';
|
|
102
|
+
ts: number;
|
|
103
|
+
data: Record<string, unknown>;
|
|
104
|
+
}
|
|
105
|
+
interface TagEvent {
|
|
106
|
+
type: 'tag';
|
|
107
|
+
ts: number;
|
|
108
|
+
data: {
|
|
109
|
+
tag: string;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
95
112
|
interface WireframeNode {
|
|
96
113
|
id: number;
|
|
97
114
|
type: string;
|
|
@@ -117,7 +134,7 @@ interface WireframeEvent {
|
|
|
117
134
|
version: number;
|
|
118
135
|
nodes: WireframeNode[];
|
|
119
136
|
}
|
|
120
|
-
type ReplayEvent = MetaEvent | ScreenEvent | LifecycleEvent | NavigationEvent | TouchEvent | ErrorEvent | LogEvent | IdentifyEvent | NetworkEvent | WireframeEvent;
|
|
137
|
+
type ReplayEvent = MetaEvent | ScreenEvent | LifecycleEvent | NavigationEvent | TouchEvent | ErrorEvent | LogEvent | IdentifyEvent | NetworkEvent | WireframeEvent | MetadataEvent | TagEvent;
|
|
121
138
|
interface BackendEvent {
|
|
122
139
|
type: string;
|
|
123
140
|
timestamp: number;
|
|
@@ -390,6 +407,13 @@ interface InternalConfig {
|
|
|
390
407
|
debug: boolean;
|
|
391
408
|
captureWireframe: boolean;
|
|
392
409
|
}
|
|
410
|
+
declare const METADATA_MAX_KEYS = 50;
|
|
411
|
+
declare const METADATA_MAX_DEPTH = 3;
|
|
412
|
+
declare const METADATA_MAX_STRING_LENGTH = 1024;
|
|
413
|
+
declare const METADATA_MAX_BYTES = 8192;
|
|
414
|
+
declare const TAG_MAX_LENGTH = 128;
|
|
415
|
+
declare function sanitizeTag(rawTag: unknown): string | null;
|
|
416
|
+
declare function sanitizeMetadata(data: unknown): Record<string, unknown> | null;
|
|
393
417
|
declare function formatEventForBatch(event: ReplayEvent | BackendEvent, defaultSessionId: string): BackendEvent;
|
|
394
418
|
declare function init(rawConfig: ClintplayReactNativeConfig): typeof Clintplay;
|
|
395
419
|
declare function identify(userId: string | number | null, traits?: Record<string, unknown>): void;
|
|
@@ -398,6 +422,8 @@ declare function reset(options?: {
|
|
|
398
422
|
}): void;
|
|
399
423
|
declare function trackScreen(path: string, from?: string): void;
|
|
400
424
|
declare function reportError(err: unknown): void;
|
|
425
|
+
declare function setMetadata(data: Record<string, unknown>): void;
|
|
426
|
+
declare function tag(tagName: string): void;
|
|
401
427
|
declare function flush(): Promise<void>;
|
|
402
428
|
declare function stop(): Promise<void>;
|
|
403
429
|
declare function current(): {
|
|
@@ -423,10 +449,12 @@ declare const Clintplay: {
|
|
|
423
449
|
reset: typeof reset;
|
|
424
450
|
trackScreen: typeof trackScreen;
|
|
425
451
|
reportError: typeof reportError;
|
|
452
|
+
setMetadata: typeof setMetadata;
|
|
453
|
+
tag: typeof tag;
|
|
426
454
|
flush: typeof flush;
|
|
427
455
|
stop: typeof stop;
|
|
428
456
|
current: typeof current;
|
|
429
457
|
_getState: typeof _getState;
|
|
430
458
|
};
|
|
431
459
|
|
|
432
|
-
export { type BackendEvent, CLINTPLAY_MASK_ID, Clintplay, ClintplayCaptureProvider, type ClintplayCaptureProviderProps, ClintplayMaskImage, type ClintplayMaskImageProps, ClintplayMaskTextInput, type ClintplayMaskTextInputProps, ClintplayMaskView, type ClintplayMaskViewProps, ClintplayNavigationTracker, type ClintplayNavigationTrackerProps, type ClintplayReactNativeConfig, type DeviceInfo, type NavigationRouteLike, type NavigationStateLike, type NavigationStateListenerCallback, type NavigationStateListenerOptions, type ReplayBatch, type ReplayEvent, SDK_VERSION, type UseClintplayNavigationOptions, type UseClintplayNavigationResult, type WireframeEvent, type WireframeNode, _resetLastTrackedRoute, createClintplayNavigationStateListener, formatEventForBatch, getActiveRouteName, init, sanitizeRoute, useClintplayNavigationTracking };
|
|
460
|
+
export { type BackendEvent, CLINTPLAY_MASK_ID, Clintplay, ClintplayCaptureProvider, type ClintplayCaptureProviderProps, ClintplayMaskImage, type ClintplayMaskImageProps, ClintplayMaskTextInput, type ClintplayMaskTextInputProps, ClintplayMaskView, type ClintplayMaskViewProps, ClintplayNavigationTracker, type ClintplayNavigationTrackerProps, type ClintplayReactNativeConfig, type DeviceInfo, METADATA_MAX_BYTES, METADATA_MAX_DEPTH, METADATA_MAX_KEYS, METADATA_MAX_STRING_LENGTH, type MetadataEvent, type NavigationRouteLike, type NavigationStateLike, type NavigationStateListenerCallback, type NavigationStateListenerOptions, type ReplayBatch, type ReplayEvent, SDK_VERSION, TAG_MAX_LENGTH, type TagEvent, type UseClintplayNavigationOptions, type UseClintplayNavigationResult, type WireframeEvent, type WireframeNode, _resetLastTrackedRoute, createClintplayNavigationStateListener, formatEventForBatch, getActiveRouteName, init, sanitizeMetadata, sanitizeRoute, sanitizeTag, setMetadata, tag, useClintplayNavigationTracking };
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ interface SessionState {
|
|
|
7
7
|
startedAt: number;
|
|
8
8
|
lastActivity: number;
|
|
9
9
|
userId: string | null;
|
|
10
|
+
tags: Set<string>;
|
|
11
|
+
metadata: Record<string, unknown>;
|
|
10
12
|
}
|
|
11
13
|
declare class SessionManager {
|
|
12
14
|
private state;
|
|
@@ -15,13 +17,16 @@ declare class SessionManager {
|
|
|
15
17
|
private create;
|
|
16
18
|
current(): SessionState;
|
|
17
19
|
touch(): void;
|
|
20
|
+
addTag(tag: string): boolean;
|
|
21
|
+
hasTag(tag: string): boolean;
|
|
22
|
+
setMetadata(data: Record<string, unknown>): void;
|
|
18
23
|
setUser(userId: string | null): boolean;
|
|
19
24
|
isStale(): boolean;
|
|
20
25
|
rotateIfStale(): boolean;
|
|
21
26
|
reset(): void;
|
|
22
27
|
}
|
|
23
28
|
|
|
24
|
-
type EventType = 'meta' | 'screen' | 'lifecycle' | 'navigation' | 'touch' | 'error' | 'log' | 'identify' | 'network' | 'wireframe';
|
|
29
|
+
type EventType = 'meta' | 'screen' | 'lifecycle' | 'navigation' | 'touch' | 'error' | 'log' | 'identify' | 'network' | 'wireframe' | 'metadata' | 'tag';
|
|
25
30
|
interface DeviceInfo {
|
|
26
31
|
platform: 'ios' | 'android' | 'web';
|
|
27
32
|
osVersion?: string;
|
|
@@ -92,6 +97,18 @@ interface NetworkEvent {
|
|
|
92
97
|
durationMs?: number;
|
|
93
98
|
error?: string;
|
|
94
99
|
}
|
|
100
|
+
interface MetadataEvent {
|
|
101
|
+
type: 'metadata';
|
|
102
|
+
ts: number;
|
|
103
|
+
data: Record<string, unknown>;
|
|
104
|
+
}
|
|
105
|
+
interface TagEvent {
|
|
106
|
+
type: 'tag';
|
|
107
|
+
ts: number;
|
|
108
|
+
data: {
|
|
109
|
+
tag: string;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
95
112
|
interface WireframeNode {
|
|
96
113
|
id: number;
|
|
97
114
|
type: string;
|
|
@@ -117,7 +134,7 @@ interface WireframeEvent {
|
|
|
117
134
|
version: number;
|
|
118
135
|
nodes: WireframeNode[];
|
|
119
136
|
}
|
|
120
|
-
type ReplayEvent = MetaEvent | ScreenEvent | LifecycleEvent | NavigationEvent | TouchEvent | ErrorEvent | LogEvent | IdentifyEvent | NetworkEvent | WireframeEvent;
|
|
137
|
+
type ReplayEvent = MetaEvent | ScreenEvent | LifecycleEvent | NavigationEvent | TouchEvent | ErrorEvent | LogEvent | IdentifyEvent | NetworkEvent | WireframeEvent | MetadataEvent | TagEvent;
|
|
121
138
|
interface BackendEvent {
|
|
122
139
|
type: string;
|
|
123
140
|
timestamp: number;
|
|
@@ -390,6 +407,13 @@ interface InternalConfig {
|
|
|
390
407
|
debug: boolean;
|
|
391
408
|
captureWireframe: boolean;
|
|
392
409
|
}
|
|
410
|
+
declare const METADATA_MAX_KEYS = 50;
|
|
411
|
+
declare const METADATA_MAX_DEPTH = 3;
|
|
412
|
+
declare const METADATA_MAX_STRING_LENGTH = 1024;
|
|
413
|
+
declare const METADATA_MAX_BYTES = 8192;
|
|
414
|
+
declare const TAG_MAX_LENGTH = 128;
|
|
415
|
+
declare function sanitizeTag(rawTag: unknown): string | null;
|
|
416
|
+
declare function sanitizeMetadata(data: unknown): Record<string, unknown> | null;
|
|
393
417
|
declare function formatEventForBatch(event: ReplayEvent | BackendEvent, defaultSessionId: string): BackendEvent;
|
|
394
418
|
declare function init(rawConfig: ClintplayReactNativeConfig): typeof Clintplay;
|
|
395
419
|
declare function identify(userId: string | number | null, traits?: Record<string, unknown>): void;
|
|
@@ -398,6 +422,8 @@ declare function reset(options?: {
|
|
|
398
422
|
}): void;
|
|
399
423
|
declare function trackScreen(path: string, from?: string): void;
|
|
400
424
|
declare function reportError(err: unknown): void;
|
|
425
|
+
declare function setMetadata(data: Record<string, unknown>): void;
|
|
426
|
+
declare function tag(tagName: string): void;
|
|
401
427
|
declare function flush(): Promise<void>;
|
|
402
428
|
declare function stop(): Promise<void>;
|
|
403
429
|
declare function current(): {
|
|
@@ -423,10 +449,12 @@ declare const Clintplay: {
|
|
|
423
449
|
reset: typeof reset;
|
|
424
450
|
trackScreen: typeof trackScreen;
|
|
425
451
|
reportError: typeof reportError;
|
|
452
|
+
setMetadata: typeof setMetadata;
|
|
453
|
+
tag: typeof tag;
|
|
426
454
|
flush: typeof flush;
|
|
427
455
|
stop: typeof stop;
|
|
428
456
|
current: typeof current;
|
|
429
457
|
_getState: typeof _getState;
|
|
430
458
|
};
|
|
431
459
|
|
|
432
|
-
export { type BackendEvent, CLINTPLAY_MASK_ID, Clintplay, ClintplayCaptureProvider, type ClintplayCaptureProviderProps, ClintplayMaskImage, type ClintplayMaskImageProps, ClintplayMaskTextInput, type ClintplayMaskTextInputProps, ClintplayMaskView, type ClintplayMaskViewProps, ClintplayNavigationTracker, type ClintplayNavigationTrackerProps, type ClintplayReactNativeConfig, type DeviceInfo, type NavigationRouteLike, type NavigationStateLike, type NavigationStateListenerCallback, type NavigationStateListenerOptions, type ReplayBatch, type ReplayEvent, SDK_VERSION, type UseClintplayNavigationOptions, type UseClintplayNavigationResult, type WireframeEvent, type WireframeNode, _resetLastTrackedRoute, createClintplayNavigationStateListener, formatEventForBatch, getActiveRouteName, init, sanitizeRoute, useClintplayNavigationTracking };
|
|
460
|
+
export { type BackendEvent, CLINTPLAY_MASK_ID, Clintplay, ClintplayCaptureProvider, type ClintplayCaptureProviderProps, ClintplayMaskImage, type ClintplayMaskImageProps, ClintplayMaskTextInput, type ClintplayMaskTextInputProps, ClintplayMaskView, type ClintplayMaskViewProps, ClintplayNavigationTracker, type ClintplayNavigationTrackerProps, type ClintplayReactNativeConfig, type DeviceInfo, METADATA_MAX_BYTES, METADATA_MAX_DEPTH, METADATA_MAX_KEYS, METADATA_MAX_STRING_LENGTH, type MetadataEvent, type NavigationRouteLike, type NavigationStateLike, type NavigationStateListenerCallback, type NavigationStateListenerOptions, type ReplayBatch, type ReplayEvent, SDK_VERSION, TAG_MAX_LENGTH, type TagEvent, type UseClintplayNavigationOptions, type UseClintplayNavigationResult, type WireframeEvent, type WireframeNode, _resetLastTrackedRoute, createClintplayNavigationStateListener, formatEventForBatch, getActiveRouteName, init, sanitizeMetadata, sanitizeRoute, sanitizeTag, setMetadata, tag, useClintplayNavigationTracking };
|
package/dist/index.js
CHANGED
|
@@ -55,7 +55,14 @@ var SessionManager = class {
|
|
|
55
55
|
}
|
|
56
56
|
create() {
|
|
57
57
|
const ts = this.now();
|
|
58
|
-
return {
|
|
58
|
+
return {
|
|
59
|
+
sessionId: uuid(),
|
|
60
|
+
startedAt: ts,
|
|
61
|
+
lastActivity: ts,
|
|
62
|
+
userId: null,
|
|
63
|
+
tags: /* @__PURE__ */ new Set(),
|
|
64
|
+
metadata: {}
|
|
65
|
+
};
|
|
59
66
|
}
|
|
60
67
|
current() {
|
|
61
68
|
return this.state;
|
|
@@ -63,6 +70,19 @@ var SessionManager = class {
|
|
|
63
70
|
touch() {
|
|
64
71
|
this.state.lastActivity = this.now();
|
|
65
72
|
}
|
|
73
|
+
addTag(tag2) {
|
|
74
|
+
if (this.state.tags.has(tag2)) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
this.state.tags.add(tag2);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
hasTag(tag2) {
|
|
81
|
+
return this.state.tags.has(tag2);
|
|
82
|
+
}
|
|
83
|
+
setMetadata(data) {
|
|
84
|
+
this.state.metadata = { ...this.state.metadata, ...data };
|
|
85
|
+
}
|
|
66
86
|
setUser(userId) {
|
|
67
87
|
const prev = this.state.userId;
|
|
68
88
|
if (prev !== null && (userId === null || prev !== userId)) {
|
|
@@ -917,7 +937,7 @@ function captureViewHierarchy() {
|
|
|
917
937
|
}
|
|
918
938
|
|
|
919
939
|
// package.json
|
|
920
|
-
var version = "0.2.
|
|
940
|
+
var version = "0.2.2";
|
|
921
941
|
var CLINTPLAY_MASK_ID = "clintplay-mask";
|
|
922
942
|
var ClintplayMaskView = React5__default.default.forwardRef(function ClintplayMaskView2({ children, testID, ...rest }, ref) {
|
|
923
943
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1229,6 +1249,124 @@ function ClintplayNavigationTracker({
|
|
|
1229
1249
|
// src/index.ts
|
|
1230
1250
|
var SDK_VERSION = version;
|
|
1231
1251
|
var state = null;
|
|
1252
|
+
var METADATA_MAX_KEYS = 50;
|
|
1253
|
+
var METADATA_MAX_DEPTH = 3;
|
|
1254
|
+
var METADATA_MAX_STRING_LENGTH = 1024;
|
|
1255
|
+
var METADATA_MAX_BYTES = 8192;
|
|
1256
|
+
var TAG_MAX_LENGTH = 128;
|
|
1257
|
+
var pendingMetadata = {};
|
|
1258
|
+
var pendingTags = /* @__PURE__ */ new Set();
|
|
1259
|
+
function sanitizeTag(rawTag) {
|
|
1260
|
+
if (typeof rawTag !== "string") return null;
|
|
1261
|
+
const trimmed = rawTag.trim();
|
|
1262
|
+
if (trimmed.length === 0) return null;
|
|
1263
|
+
const normalized = typeof trimmed.normalize === "function" ? trimmed.normalize("NFC") : trimmed;
|
|
1264
|
+
return normalized.slice(0, TAG_MAX_LENGTH);
|
|
1265
|
+
}
|
|
1266
|
+
function isPlainObject(val) {
|
|
1267
|
+
return typeof val === "object" && val !== null && !Array.isArray(val);
|
|
1268
|
+
}
|
|
1269
|
+
function sanitizeMetadataValue(val, depth, seen) {
|
|
1270
|
+
if (val === null || val === void 0) {
|
|
1271
|
+
return val;
|
|
1272
|
+
}
|
|
1273
|
+
if (typeof val === "function" || typeof val === "symbol") {
|
|
1274
|
+
return void 0;
|
|
1275
|
+
}
|
|
1276
|
+
if (typeof val === "string") {
|
|
1277
|
+
return val.slice(0, METADATA_MAX_STRING_LENGTH);
|
|
1278
|
+
}
|
|
1279
|
+
if (typeof val === "number") {
|
|
1280
|
+
return Number.isFinite(val) ? val : null;
|
|
1281
|
+
}
|
|
1282
|
+
if (typeof val === "boolean") {
|
|
1283
|
+
return val;
|
|
1284
|
+
}
|
|
1285
|
+
if (typeof val === "bigint") {
|
|
1286
|
+
return val.toString();
|
|
1287
|
+
}
|
|
1288
|
+
if (val instanceof Date) {
|
|
1289
|
+
return Number.isNaN(val.getTime()) ? null : val.toISOString();
|
|
1290
|
+
}
|
|
1291
|
+
if (typeof val === "object") {
|
|
1292
|
+
if (seen.has(val)) {
|
|
1293
|
+
return "[Circular]";
|
|
1294
|
+
}
|
|
1295
|
+
seen.add(val);
|
|
1296
|
+
try {
|
|
1297
|
+
if (depth > METADATA_MAX_DEPTH) {
|
|
1298
|
+
return void 0;
|
|
1299
|
+
}
|
|
1300
|
+
if (Array.isArray(val)) {
|
|
1301
|
+
const arr = [];
|
|
1302
|
+
for (const item of val) {
|
|
1303
|
+
const cleaned = sanitizeMetadataValue(item, depth + 1, seen);
|
|
1304
|
+
if (cleaned !== void 0) {
|
|
1305
|
+
arr.push(cleaned);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
return arr;
|
|
1309
|
+
}
|
|
1310
|
+
const obj = {};
|
|
1311
|
+
const entries = Object.entries(val);
|
|
1312
|
+
let keyCount = 0;
|
|
1313
|
+
for (const [k, v] of entries) {
|
|
1314
|
+
if (k === "__proto__" || k === "constructor" || k === "prototype") {
|
|
1315
|
+
continue;
|
|
1316
|
+
}
|
|
1317
|
+
if (keyCount >= METADATA_MAX_KEYS) {
|
|
1318
|
+
break;
|
|
1319
|
+
}
|
|
1320
|
+
const cleaned = sanitizeMetadataValue(v, depth + 1, seen);
|
|
1321
|
+
if (cleaned !== void 0) {
|
|
1322
|
+
obj[k] = cleaned;
|
|
1323
|
+
keyCount++;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
return obj;
|
|
1327
|
+
} finally {
|
|
1328
|
+
seen.delete(val);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
return void 0;
|
|
1332
|
+
}
|
|
1333
|
+
function sanitizeMetadata(data) {
|
|
1334
|
+
if (!isPlainObject(data)) {
|
|
1335
|
+
return null;
|
|
1336
|
+
}
|
|
1337
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1338
|
+
seen.add(data);
|
|
1339
|
+
const result = {};
|
|
1340
|
+
const entries = Object.entries(data);
|
|
1341
|
+
let keyCount = 0;
|
|
1342
|
+
for (const [k, v] of entries) {
|
|
1343
|
+
if (k === "__proto__" || k === "constructor" || k === "prototype") {
|
|
1344
|
+
continue;
|
|
1345
|
+
}
|
|
1346
|
+
if (keyCount >= METADATA_MAX_KEYS) {
|
|
1347
|
+
break;
|
|
1348
|
+
}
|
|
1349
|
+
const cleaned = sanitizeMetadataValue(v, 1, seen);
|
|
1350
|
+
if (cleaned !== void 0) {
|
|
1351
|
+
result[k] = cleaned;
|
|
1352
|
+
keyCount++;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
try {
|
|
1356
|
+
let json = JSON.stringify(result);
|
|
1357
|
+
if (json.length > METADATA_MAX_BYTES) {
|
|
1358
|
+
const keys = Object.keys(result);
|
|
1359
|
+
while (keys.length > 0 && json.length > METADATA_MAX_BYTES) {
|
|
1360
|
+
const lastKey = keys.pop();
|
|
1361
|
+
delete result[lastKey];
|
|
1362
|
+
json = JSON.stringify(result);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
} catch {
|
|
1366
|
+
return null;
|
|
1367
|
+
}
|
|
1368
|
+
return result;
|
|
1369
|
+
}
|
|
1232
1370
|
function deviceInfo() {
|
|
1233
1371
|
const os = reactNative.Platform.OS;
|
|
1234
1372
|
const platform = os === "ios" ? "ios" : os === "android" ? "android" : "web";
|
|
@@ -1374,6 +1512,30 @@ function init(rawConfig) {
|
|
|
1374
1512
|
});
|
|
1375
1513
|
}
|
|
1376
1514
|
pushSessionMeta(local);
|
|
1515
|
+
if (Object.keys(pendingMetadata).length > 0) {
|
|
1516
|
+
local.session.setMetadata(pendingMetadata);
|
|
1517
|
+
if (!excluded.has("metadata")) {
|
|
1518
|
+
local.buffer.push({
|
|
1519
|
+
type: "metadata",
|
|
1520
|
+
ts: Date.now(),
|
|
1521
|
+
data: pendingMetadata
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
pendingMetadata = {};
|
|
1525
|
+
}
|
|
1526
|
+
if (pendingTags.size > 0) {
|
|
1527
|
+
for (const t of pendingTags) {
|
|
1528
|
+
local.session.addTag(t);
|
|
1529
|
+
if (!excluded.has("tag")) {
|
|
1530
|
+
local.buffer.push({
|
|
1531
|
+
type: "tag",
|
|
1532
|
+
ts: Date.now(),
|
|
1533
|
+
data: { tag: t }
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
pendingTags.clear();
|
|
1538
|
+
}
|
|
1377
1539
|
recorder.start();
|
|
1378
1540
|
local.networkInterceptor?.start();
|
|
1379
1541
|
local.consoleInterceptor?.start();
|
|
@@ -1465,11 +1627,54 @@ function reportError(err) {
|
|
|
1465
1627
|
fatal: false
|
|
1466
1628
|
});
|
|
1467
1629
|
}
|
|
1630
|
+
function setMetadata(data) {
|
|
1631
|
+
const sanitized = sanitizeMetadata(data);
|
|
1632
|
+
if (!sanitized || Object.keys(sanitized).length === 0) return;
|
|
1633
|
+
if (!state?.active) {
|
|
1634
|
+
pendingMetadata = { ...pendingMetadata, ...sanitized };
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
state.session.setMetadata(sanitized);
|
|
1638
|
+
state.session.touch();
|
|
1639
|
+
if (!state.excluded.has("metadata")) {
|
|
1640
|
+
state.buffer.push({
|
|
1641
|
+
type: "metadata",
|
|
1642
|
+
ts: Date.now(),
|
|
1643
|
+
data: sanitized
|
|
1644
|
+
});
|
|
1645
|
+
if (state.buffer.size() >= state.config.flushSize) {
|
|
1646
|
+
void flushOnce(state);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
function tag(tagName) {
|
|
1651
|
+
const cleaned = sanitizeTag(tagName);
|
|
1652
|
+
if (!cleaned) return;
|
|
1653
|
+
if (!state?.active) {
|
|
1654
|
+
pendingTags.add(cleaned);
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
if (state.session.addTag(cleaned)) {
|
|
1658
|
+
state.session.touch();
|
|
1659
|
+
if (!state.excluded.has("tag")) {
|
|
1660
|
+
state.buffer.push({
|
|
1661
|
+
type: "tag",
|
|
1662
|
+
ts: Date.now(),
|
|
1663
|
+
data: { tag: cleaned }
|
|
1664
|
+
});
|
|
1665
|
+
if (state.buffer.size() >= state.config.flushSize) {
|
|
1666
|
+
void flushOnce(state);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1468
1671
|
async function flush() {
|
|
1469
1672
|
if (!state?.active) return;
|
|
1470
1673
|
await flushOnce(state);
|
|
1471
1674
|
}
|
|
1472
1675
|
async function stop() {
|
|
1676
|
+
pendingMetadata = {};
|
|
1677
|
+
pendingTags.clear();
|
|
1473
1678
|
if (!state?.active) return;
|
|
1474
1679
|
const s = state;
|
|
1475
1680
|
s.active = false;
|
|
@@ -1499,6 +1704,8 @@ var Clintplay = {
|
|
|
1499
1704
|
reset,
|
|
1500
1705
|
trackScreen,
|
|
1501
1706
|
reportError,
|
|
1707
|
+
setMetadata,
|
|
1708
|
+
tag,
|
|
1502
1709
|
flush,
|
|
1503
1710
|
stop,
|
|
1504
1711
|
current,
|
|
@@ -1512,13 +1719,22 @@ exports.ClintplayMaskImage = ClintplayMaskImage;
|
|
|
1512
1719
|
exports.ClintplayMaskTextInput = ClintplayMaskTextInput;
|
|
1513
1720
|
exports.ClintplayMaskView = ClintplayMaskView;
|
|
1514
1721
|
exports.ClintplayNavigationTracker = ClintplayNavigationTracker;
|
|
1722
|
+
exports.METADATA_MAX_BYTES = METADATA_MAX_BYTES;
|
|
1723
|
+
exports.METADATA_MAX_DEPTH = METADATA_MAX_DEPTH;
|
|
1724
|
+
exports.METADATA_MAX_KEYS = METADATA_MAX_KEYS;
|
|
1725
|
+
exports.METADATA_MAX_STRING_LENGTH = METADATA_MAX_STRING_LENGTH;
|
|
1515
1726
|
exports.SDK_VERSION = SDK_VERSION;
|
|
1727
|
+
exports.TAG_MAX_LENGTH = TAG_MAX_LENGTH;
|
|
1516
1728
|
exports._resetLastTrackedRoute = _resetLastTrackedRoute;
|
|
1517
1729
|
exports.createClintplayNavigationStateListener = createClintplayNavigationStateListener;
|
|
1518
1730
|
exports.formatEventForBatch = formatEventForBatch;
|
|
1519
1731
|
exports.getActiveRouteName = getActiveRouteName;
|
|
1520
1732
|
exports.init = init;
|
|
1733
|
+
exports.sanitizeMetadata = sanitizeMetadata;
|
|
1521
1734
|
exports.sanitizeRoute = sanitizeRoute;
|
|
1735
|
+
exports.sanitizeTag = sanitizeTag;
|
|
1736
|
+
exports.setMetadata = setMetadata;
|
|
1737
|
+
exports.tag = tag;
|
|
1522
1738
|
exports.useClintplayNavigationTracking = useClintplayNavigationTracking;
|
|
1523
1739
|
//# sourceMappingURL=index.js.map
|
|
1524
1740
|
//# sourceMappingURL=index.js.map
|