@akhilpulse/samadhaan-session-replay 0.2.2 → 0.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akhilpulse/samadhaan-session-replay",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "React Native SDK for Samadhaan Guided Support — session recording, live remote screen-share, take-control, and guided-tour overlays.",
5
5
  "main": "src/index.ts",
6
6
  "module": "src/index.ts",
@@ -35,7 +35,7 @@ import AsyncStorage from "@react-native-async-storage/async-storage";
35
35
  import { captureRef } from "react-native-view-shot";
36
36
 
37
37
  const STORAGE_KEY = "@akhilpulse/samadhaan-session-replay/sessionId";
38
- const SDK_VERSION = "0.2.2";
38
+ const SDK_VERSION = "0.2.4";
39
39
 
40
40
  /** Default Samadhaan production endpoint. Override via `config.serverUrl` for staging. */
41
41
  export const DEFAULT_SERVER_URL = "https://samadhaan.pulseenergy.io";
@@ -211,58 +211,50 @@ export function SessionReplayProvider({ config, children }: { config: Config; ch
211
211
  // ---- Screenshot capture ----
212
212
  const liveMode = status === "live";
213
213
  useEffect(() => {
214
- if (config.disableScreenshots || !sessionId) return;
214
+ // Only capture screenshots in live mode. Idle sessions emit interaction
215
+ // events (taps, navigation, etc.) but do not waste CPU/battery/network on
216
+ // periodic screenshots — frames stream only when an agent is actively viewing.
217
+ if (config.disableScreenshots || !sessionId || !liveMode) return;
215
218
  let stopped = false;
216
- // Live: ~16 fps target (60ms). Idle: 10s sample for replay.
217
- // Capture itself takes time, so the loop self-paces we schedule the next
218
- // tick AFTER the previous capture/upload completes rather than at a fixed cadence.
219
- const idleInterval = 10000;
220
- const liveTargetMs = 60;
219
+ // ~2 fps target — minimal device load. Capture takes time, so the loop
220
+ // self-paces: we schedule the next tick AFTER the previous capture completes.
221
+ const liveTargetMs = 500;
221
222
  let lastUploadFrameAt = 0;
222
223
  const tick = async () => {
223
224
  if (stopped) return;
224
225
  const startedAt = Date.now();
225
226
  if (paused || keyboardVisible) {
226
- setTimeout(tick, liveMode ? liveTargetMs : idleInterval);
227
+ setTimeout(tick, liveTargetMs);
227
228
  return;
228
229
  }
229
230
  try {
230
231
  const ref = rootRef.current;
231
232
  if (ref) {
232
- // Live frames are aggressively downscaled + low quality so capture+encode stays under ~50ms on mid devices.
233
+ // Aggressively downscaled + low quality so capture+encode stays under ~50ms on mid devices.
233
234
  const uri = await captureRef(ref as any, {
234
235
  format: "jpg",
235
- quality: liveMode ? 0.25 : 0.45,
236
+ quality: 0.25,
236
237
  result: "base64",
237
- width: liveMode ? 360 : 540,
238
+ width: 360,
238
239
  });
239
240
  const blob = base64ToUint8Array(uri);
240
-
241
- if (liveMode) {
242
- // ---- LIVE PATH: stream binary over WS only. NO REST upload (too slow). ----
243
- if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
244
- sendBinaryFrame(wsRef.current, blob, 0, 0);
245
- }
246
- // Persist a sampled keyframe at most once every 10s for the replay timeline.
247
- if (startedAt - lastUploadFrameAt > 10000) {
248
- lastUploadFrameAt = startedAt;
249
- uploadFrameAsync(serverUrl, sessionId, ingestToken, uri, blob).then((path) => {
250
- if (path) queueEvent("screenshot", { w: 0, h: 0 }, path);
251
- }).catch(() => {});
252
- }
253
- } else {
254
- // ---- IDLE PATH: upload for replay. ----
255
- const path = await uploadFrameAsync(serverUrl, sessionId, ingestToken, uri, blob);
256
- queueEvent("screenshot", { w: 0, h: 0 }, path || undefined);
241
+ // Stream binary over WS — no REST upload in the hot path.
242
+ if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
243
+ sendBinaryFrame(wsRef.current, blob, 0, 0);
244
+ }
245
+ // Persist a sampled keyframe at most once every 10s for the replay timeline.
246
+ if (startedAt - lastUploadFrameAt > 10000) {
247
+ lastUploadFrameAt = startedAt;
248
+ uploadFrameAsync(serverUrl, sessionId, ingestToken, uri, blob).then((path) => {
249
+ if (path) queueEvent("screenshot", { w: 0, h: 0 }, path);
250
+ }).catch(() => {});
257
251
  }
258
252
  }
259
253
  } catch {}
260
- // Self-pacing: schedule next tick relative to how long this one took.
261
254
  const elapsed = Date.now() - startedAt;
262
- const next = liveMode ? Math.max(0, liveTargetMs - elapsed) : Math.max(0, idleInterval - elapsed);
263
- setTimeout(tick, next);
255
+ setTimeout(tick, Math.max(0, liveTargetMs - elapsed));
264
256
  };
265
- setTimeout(tick, liveMode ? liveTargetMs : idleInterval);
257
+ setTimeout(tick, liveTargetMs);
266
258
  return () => { stopped = true; };
267
259
  }, [sessionId, ingestToken, liveMode, paused, keyboardVisible, config.disableScreenshots, serverUrl, queueEvent]);
268
260