@akhilpulse/samadhaan-session-replay 0.2.2 → 0.2.3
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 +1 -1
- package/src/SessionReplayProvider.tsx +23 -31
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akhilpulse/samadhaan-session-replay",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
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.
|
|
38
|
+
const SDK_VERSION = "0.2.3";
|
|
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
|
-
|
|
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
|
-
//
|
|
217
|
-
//
|
|
218
|
-
// tick AFTER the previous capture/upload completes rather than at a fixed cadence.
|
|
219
|
-
const idleInterval = 10000;
|
|
219
|
+
// ~16 fps target. Capture takes time, so the loop self-paces — we schedule
|
|
220
|
+
// the next tick AFTER the previous capture completes rather than at a fixed cadence.
|
|
220
221
|
const liveTargetMs = 60;
|
|
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,
|
|
227
|
+
setTimeout(tick, liveTargetMs);
|
|
227
228
|
return;
|
|
228
229
|
}
|
|
229
230
|
try {
|
|
230
231
|
const ref = rootRef.current;
|
|
231
232
|
if (ref) {
|
|
232
|
-
//
|
|
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:
|
|
236
|
+
quality: 0.25,
|
|
236
237
|
result: "base64",
|
|
237
|
-
width:
|
|
238
|
+
width: 360,
|
|
238
239
|
});
|
|
239
240
|
const blob = base64ToUint8Array(uri);
|
|
240
|
-
|
|
241
|
-
if (
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
263
|
-
setTimeout(tick, next);
|
|
255
|
+
setTimeout(tick, Math.max(0, liveTargetMs - elapsed));
|
|
264
256
|
};
|
|
265
|
-
setTimeout(tick,
|
|
257
|
+
setTimeout(tick, liveTargetMs);
|
|
266
258
|
return () => { stopped = true; };
|
|
267
259
|
}, [sessionId, ingestToken, liveMode, paused, keyboardVisible, config.disableScreenshots, serverUrl, queueEvent]);
|
|
268
260
|
|