@plushanalytics/react-native-session-replay 3.0.0 → 3.0.4

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.
Files changed (40) hide show
  1. package/README.md +59 -2
  2. package/android/src/main/java/com/posthogreactnativesessionreplay/PosthogReactNativeSessionReplayModule.kt +125 -27
  3. package/ios/PosthogReactNativeSessionReplay.mm +3 -0
  4. package/ios/PosthogReactNativeSessionReplay.swift +187 -36
  5. package/lib/commonjs/adapters/mobileReplayAdapter.js +55 -18
  6. package/lib/commonjs/adapters/mobileReplayAdapter.js.map +1 -1
  7. package/lib/commonjs/client.js +2 -1
  8. package/lib/commonjs/client.js.map +1 -1
  9. package/lib/commonjs/nativeBridge.js +5 -0
  10. package/lib/commonjs/nativeBridge.js.map +1 -1
  11. package/lib/module/adapters/mobileReplayAdapter.js +55 -18
  12. package/lib/module/adapters/mobileReplayAdapter.js.map +1 -1
  13. package/lib/module/client.js +2 -1
  14. package/lib/module/client.js.map +1 -1
  15. package/lib/module/nativeBridge.js +4 -0
  16. package/lib/module/nativeBridge.js.map +1 -1
  17. package/lib/typescript/commonjs/src/adapters/mobileReplayAdapter.d.ts +2 -1
  18. package/lib/typescript/commonjs/src/adapters/mobileReplayAdapter.d.ts.map +1 -1
  19. package/lib/typescript/commonjs/src/client.d.ts.map +1 -1
  20. package/lib/typescript/commonjs/src/index.d.ts +1 -1
  21. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  22. package/lib/typescript/commonjs/src/nativeBridge.d.ts +2 -0
  23. package/lib/typescript/commonjs/src/nativeBridge.d.ts.map +1 -1
  24. package/lib/typescript/commonjs/src/types.d.ts +16 -5
  25. package/lib/typescript/commonjs/src/types.d.ts.map +1 -1
  26. package/lib/typescript/module/src/adapters/mobileReplayAdapter.d.ts +2 -1
  27. package/lib/typescript/module/src/adapters/mobileReplayAdapter.d.ts.map +1 -1
  28. package/lib/typescript/module/src/client.d.ts.map +1 -1
  29. package/lib/typescript/module/src/index.d.ts +1 -1
  30. package/lib/typescript/module/src/index.d.ts.map +1 -1
  31. package/lib/typescript/module/src/nativeBridge.d.ts +2 -0
  32. package/lib/typescript/module/src/nativeBridge.d.ts.map +1 -1
  33. package/lib/typescript/module/src/types.d.ts +16 -5
  34. package/lib/typescript/module/src/types.d.ts.map +1 -1
  35. package/package.json +1 -1
  36. package/src/adapters/mobileReplayAdapter.ts +112 -19
  37. package/src/client.ts +2 -1
  38. package/src/index.tsx +2 -0
  39. package/src/nativeBridge.ts +8 -1
  40. package/src/types.ts +19 -5
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,SAAS,EACT,WAAW,EACX,YAAY,EACZ,eAAe,EACf,SAAS,EACT,cAAc,IAAI,kBAAkB,EACpC,oBAAoB,IAAI,UAAU,EAClC,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,YAAY,EACZ,aAAa,EACb,uBAAuB,IAAI,2BAA2B,EACtD,cAAc,EACd,YAAY,EACZ,mBAAmB,EACpB,MAAM,4BAA4B,CAAC;AAEpC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,2BAA2B,GAAG;IAElE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,kBAAkB,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,UAAU,CAAC;AAE9C,YAAY,EACV,aAAa,EACb,SAAS,EACT,WAAW,EACX,YAAY,EACZ,eAAe,EACf,SAAS,EACT,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,YAAY,EACZ,aAAa,EACb,cAAc,EACd,YAAY,EACZ,mBAAmB,EACpB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,kBAAkB,EAAE,uBAAuB,GAAG,aAAa,CAAC,GAAG;IAC/F,qBAAqB,EAAE,CAAC,OAAO,CAAC,EAAE,uBAAuB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5E,WAAW,EAAE,CAAC,OAAO,CAAC,EAAE,uBAAuB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,SAAS,EACT,WAAW,EACX,YAAY,EACZ,eAAe,EACf,SAAS,EACT,cAAc,IAAI,kBAAkB,EACpC,oBAAoB,IAAI,UAAU,EAClC,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,YAAY,EACZ,aAAa,EACb,uBAAuB,IAAI,2BAA2B,EACtD,cAAc,EACd,YAAY,EACZ,mBAAmB,EACpB,MAAM,4BAA4B,CAAC;AAEpC,MAAM,MAAM,0BAA0B,GAAG;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,0BAA0B,CAAC;IACrC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,GAAG;IAErD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,2BAA2B,GAAG;IAElE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,kBAAkB,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG;IAC9C,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CAC3C,CAAC;AAEF,YAAY,EACV,aAAa,EACb,SAAS,EACT,WAAW,EACX,YAAY,EACZ,eAAe,EACf,SAAS,EACT,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,YAAY,EACZ,aAAa,EACb,cAAc,EACd,YAAY,EACZ,mBAAmB,EACpB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC,kBAAkB,EAAE,uBAAuB,GAAG,aAAa,CAAC,GAAG;IAC/F,qBAAqB,EAAE,CAAC,OAAO,CAAC,EAAE,uBAAuB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5E,WAAW,EAAE,CAAC,OAAO,CAAC,EAAE,uBAAuB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plushanalytics/react-native-session-replay",
3
- "version": "3.0.0",
3
+ "version": "3.0.4",
4
4
  "description": "React Native SDK for Plush Analytics events + session replay ingestion.",
5
5
  "source": "./src/index.tsx",
6
6
  "main": "./lib/commonjs/index.js",
@@ -1,12 +1,34 @@
1
- import type { MobileReplayConfig, ReplayRecorderAdapter, SessionRecordingOptions } from "../types";
1
+ import type {
2
+ MobileReplayConfig,
3
+ ReplayRecorderAdapter,
4
+ SessionRecordingOptions,
5
+ SessionReplayConfig,
6
+ } from "../types";
2
7
  import * as replayModule from "../nativeBridge";
3
8
 
4
9
  type AdapterDefaults = {
5
10
  apiKey: string;
6
11
  endpoint: string;
12
+ replayConfigDefaults?: SessionReplayConfig;
13
+ };
14
+
15
+ type StartArgs = {
16
+ onSessionId?: (sessionId: string) => void;
17
+ options?: SessionRecordingOptions;
18
+ };
19
+
20
+ type NormalizedReplayConfig = {
21
+ enabled: boolean;
22
+ maskAllTextInputs: boolean;
23
+ maskAllImages: boolean;
24
+ maskAllSandboxedViews: boolean;
25
+ captureLog: boolean;
26
+ captureNetworkTelemetry: boolean;
27
+ throttleDelayMs: number;
7
28
  };
8
29
 
9
30
  const DEFAULT_SNAPSHOT_PATH = "/v1/events/batch";
31
+ const DEFAULT_THROTTLE_DELAY_MS = 1000;
10
32
  const UUID_PATTERN =
11
33
  /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
12
34
 
@@ -36,22 +58,11 @@ function generateUuid(): string {
36
58
 
37
59
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (char) => {
38
60
  const random = Math.floor(Math.random() * 16);
39
- const value = char === "x" ? random : (random & 0x3) | 0x8;
40
- return value.toString(16);
61
+ const resolved = char === "x" ? random : (random & 0x3) | 0x8;
62
+ return resolved.toString(16);
41
63
  });
42
64
  }
43
65
 
44
- function normalizeReplayConfig(config: MobileReplayConfig | undefined): Record<string, unknown> {
45
- return {
46
- maskAllTextInputs: config?.maskAllTextInputs ?? true,
47
- maskAllImages: config?.maskAllImages ?? true,
48
- maskAllSandboxedViews: config?.maskAllSandboxedViews ?? true,
49
- captureLog: config?.captureLog ?? true,
50
- captureNetworkTelemetry: config?.captureNetworkTelemetry ?? true,
51
- throttleDelayMs: config?.throttleDelayMs ?? 1000
52
- };
53
- }
54
-
55
66
  function resolveSessionId(options?: SessionRecordingOptions): string {
56
67
  const requestedSessionId = options?.sessionId?.trim();
57
68
  if (requestedSessionId && isUuid(requestedSessionId)) {
@@ -75,14 +86,93 @@ function assertNoPosthogDotCom(host: string): void {
75
86
  }
76
87
  }
77
88
 
89
+ function resolveMaskingValue(
90
+ overrideConfig: MobileReplayConfig | undefined,
91
+ defaultConfig: SessionReplayConfig | undefined,
92
+ maskingKey: keyof NonNullable<SessionReplayConfig["masking"]>,
93
+ legacyKey: "maskAllTextInputs" | "maskAllImages" | "maskAllSandboxedViews"
94
+ ): boolean {
95
+ const overrideMaskingValue = overrideConfig?.masking?.[maskingKey];
96
+ if (typeof overrideMaskingValue === "boolean") {
97
+ return overrideMaskingValue;
98
+ }
99
+
100
+ const overrideLegacyValue = overrideConfig?.[legacyKey];
101
+ if (typeof overrideLegacyValue === "boolean") {
102
+ return overrideLegacyValue;
103
+ }
104
+
105
+ const defaultMaskingValue = defaultConfig?.masking?.[maskingKey];
106
+ if (typeof defaultMaskingValue === "boolean") {
107
+ return defaultMaskingValue;
108
+ }
109
+
110
+ return true;
111
+ }
112
+
113
+ function resolveReplayConfig(
114
+ defaultConfig: SessionReplayConfig | undefined,
115
+ overrideConfig: MobileReplayConfig | undefined
116
+ ): NormalizedReplayConfig {
117
+ const enabled = overrideConfig?.enabled ?? defaultConfig?.enabled ?? true;
118
+ const captureLog = overrideConfig?.captureLog ?? defaultConfig?.captureLog ?? true;
119
+ const captureNetworkTelemetry =
120
+ overrideConfig?.captureNetworkTelemetry ?? defaultConfig?.captureNetworkTelemetry ?? true;
121
+
122
+ const requestedThrottleDelayMs =
123
+ overrideConfig?.throttleDelayMs ?? defaultConfig?.throttleDelayMs ?? DEFAULT_THROTTLE_DELAY_MS;
124
+ const throttleDelayMs =
125
+ Number.isFinite(requestedThrottleDelayMs) && requestedThrottleDelayMs >= 0
126
+ ? requestedThrottleDelayMs
127
+ : DEFAULT_THROTTLE_DELAY_MS;
128
+
129
+ return {
130
+ enabled,
131
+ maskAllTextInputs: resolveMaskingValue(
132
+ overrideConfig,
133
+ defaultConfig,
134
+ "textInputs",
135
+ "maskAllTextInputs"
136
+ ),
137
+ maskAllImages: resolveMaskingValue(overrideConfig, defaultConfig, "images", "maskAllImages"),
138
+ maskAllSandboxedViews: resolveMaskingValue(
139
+ overrideConfig,
140
+ defaultConfig,
141
+ "sandboxedViews",
142
+ "maskAllSandboxedViews"
143
+ ),
144
+ captureLog,
145
+ captureNetworkTelemetry,
146
+ throttleDelayMs,
147
+ };
148
+ }
149
+
150
+ function toNativeReplayConfig(config: NormalizedReplayConfig): Record<string, unknown> {
151
+ return {
152
+ maskAllTextInputs: config.maskAllTextInputs,
153
+ maskAllImages: config.maskAllImages,
154
+ maskAllSandboxedViews: config.maskAllSandboxedViews,
155
+ captureLog: config.captureLog,
156
+ captureNetworkTelemetry: config.captureNetworkTelemetry,
157
+ throttleDelayMs: config.throttleDelayMs,
158
+ };
159
+ }
160
+
78
161
  export function createMobileReplayRecorderAdapter(defaults: AdapterDefaults): ReplayRecorderAdapter {
79
162
  assertNonEmpty(defaults.apiKey, "apiKey");
80
163
  assertNonEmpty(defaults.endpoint, "endpoint");
81
164
 
82
165
  return {
83
- start: async ({ onSessionId, options }) => {
166
+ start: async (args: StartArgs) => {
167
+ const options = args.options;
168
+ const resolvedReplayConfig = resolveReplayConfig(defaults.replayConfigDefaults, options?.replayConfig);
169
+ if (!resolvedReplayConfig.enabled) {
170
+ return () => {};
171
+ }
172
+
84
173
  const host = normalizeHost(options?.replayHost?.trim() || defaults.endpoint);
85
174
  assertNoPosthogDotCom(host);
175
+
86
176
  const snapshotPath = normalizeSnapshotPath(options?.replaySnapshotPath);
87
177
  const activeSessionId = resolveSessionId(options);
88
178
 
@@ -93,21 +183,24 @@ export function createMobileReplayRecorderAdapter(defaults: AdapterDefaults): Re
93
183
  distinctId: options?.userId?.trim() || "",
94
184
  anonymousId: "",
95
185
  sdkVersion: "plushanalytics-react-native",
96
- flushAt: 20
186
+ flushAt: 20,
97
187
  };
98
188
 
99
189
  await replayModule.start(
100
190
  activeSessionId,
101
191
  sdkOptions,
102
- normalizeReplayConfig(options?.replayConfig),
192
+ toNativeReplayConfig(resolvedReplayConfig),
103
193
  { endpoint: snapshotPath }
104
194
  );
105
195
  await replayModule.startSession(activeSessionId);
106
- onSessionId?.(activeSessionId);
196
+ args.onSessionId?.(activeSessionId);
107
197
 
108
198
  return () => {
109
199
  void replayModule.endSession();
110
200
  };
111
- }
201
+ },
202
+ flush: async () => {
203
+ await replayModule.flush();
204
+ },
112
205
  };
113
206
  }
package/src/client.ts CHANGED
@@ -30,7 +30,8 @@ export function createPlushAnalyticsClient(config: PlushAnalyticsConfig): PlushA
30
30
  config.replay?.defaultRecorder ??
31
31
  createMobileReplayRecorderAdapter({
32
32
  apiKey: config.apiKey,
33
- endpoint
33
+ endpoint,
34
+ replayConfigDefaults: config.sessionReplayConfig
34
35
  })
35
36
  }
36
37
  };
package/src/index.tsx CHANGED
@@ -17,6 +17,8 @@ export type {
17
17
  RetryOptions,
18
18
  ScreenOptions,
19
19
  SessionRecordingOptions,
20
+ SessionReplayConfig,
21
+ SessionReplayMaskingConfig,
20
22
  StorageAdapter,
21
23
  TrackOptions,
22
24
  UnifiedBatchPayload
@@ -40,6 +40,10 @@ export function endSession(): Promise<void> {
40
40
  return PosthogReactNativeSessionReplay.endSession();
41
41
  }
42
42
 
43
+ export function flush(): Promise<void> {
44
+ return PosthogReactNativeSessionReplay.flush();
45
+ }
46
+
43
47
  export function isEnabled(): Promise<boolean> {
44
48
  return PosthogReactNativeSessionReplay.isEnabled();
45
49
  }
@@ -56,13 +60,15 @@ export interface PostHogReactNativeSessionReplayModule {
56
60
  sessionId: string,
57
61
  sdkOptions: { [key: string]: any }, // options from SDK such as apiKey
58
62
  sdkReplayConfig: { [key: string]: any }, // config from SDK
59
- decideReplayConfig: { [key: string]: any } // config from Decide API
63
+ decideReplayConfig: { [key: string]: any } // replay runtime config
60
64
  ) => Promise<void>;
61
65
 
62
66
  startSession: (sessionId: string) => Promise<void>;
63
67
 
64
68
  endSession: () => Promise<void>;
65
69
 
70
+ flush: () => Promise<void>;
71
+
66
72
  isEnabled: () => Promise<boolean>;
67
73
 
68
74
  identify: (distinctId: string, anonymousId: string) => Promise<void>;
@@ -72,6 +78,7 @@ const PostHogReactNativeSessionReplay: PostHogReactNativeSessionReplayModule = {
72
78
  start,
73
79
  startSession,
74
80
  endSession,
81
+ flush,
75
82
  isEnabled,
76
83
  identify,
77
84
  };
package/src/types.ts CHANGED
@@ -18,15 +18,27 @@ import type {
18
18
  UnifiedBatchPayload
19
19
  } from "@plushanalytics/javascript";
20
20
 
21
- export type MobileReplayConfig = {
22
- maskAllTextInputs?: boolean;
23
- maskAllImages?: boolean;
24
- maskAllSandboxedViews?: boolean;
21
+ export type SessionReplayMaskingConfig = {
22
+ textInputs?: boolean;
23
+ images?: boolean;
24
+ sandboxedViews?: boolean;
25
+ };
26
+
27
+ export type SessionReplayConfig = {
28
+ enabled?: boolean;
29
+ masking?: SessionReplayMaskingConfig;
25
30
  captureLog?: boolean;
26
31
  captureNetworkTelemetry?: boolean;
27
32
  throttleDelayMs?: number;
28
33
  };
29
34
 
35
+ export type MobileReplayConfig = SessionReplayConfig & {
36
+ // Legacy flat options are still supported for backward compatibility.
37
+ maskAllTextInputs?: boolean;
38
+ maskAllImages?: boolean;
39
+ maskAllSandboxedViews?: boolean;
40
+ };
41
+
30
42
  export type SessionRecordingOptions = CoreSessionRecordingOptions & {
31
43
  // Used by the bundled replay adapter (optional, replay still works without these when you pass a custom recorder).
32
44
  replayHost?: string;
@@ -34,7 +46,9 @@ export type SessionRecordingOptions = CoreSessionRecordingOptions & {
34
46
  replayConfig?: MobileReplayConfig;
35
47
  };
36
48
 
37
- export type PlushAnalyticsConfig = CoreConfig;
49
+ export type PlushAnalyticsConfig = CoreConfig & {
50
+ sessionReplayConfig?: SessionReplayConfig;
51
+ };
38
52
 
39
53
  export type {
40
54
  EventEnvelope,