@akhilpulse/samadhaan-session-replay 0.1.0 → 0.2.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akhilpulse/samadhaan-session-replay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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.
|
|
38
|
+
const SDK_VERSION = "0.2.0";
|
|
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";
|
|
@@ -173,7 +173,7 @@ export function SessionReplayProvider({ config, children }: { config: Config; ch
|
|
|
173
173
|
return () => { showSub.remove(); hideSub.remove(); };
|
|
174
174
|
}, []);
|
|
175
175
|
|
|
176
|
-
// ---- Status polling
|
|
176
|
+
// ---- Status polling: detects live transitions AND auto-end so we can recycle the session ----
|
|
177
177
|
useEffect(() => {
|
|
178
178
|
if (!sessionId) return;
|
|
179
179
|
const id = setInterval(async () => {
|
|
@@ -181,7 +181,15 @@ export function SessionReplayProvider({ config, children }: { config: Config; ch
|
|
|
181
181
|
const r = await fetch(`${serverUrl}/api/guided-support/sessions/${sessionId}/status`);
|
|
182
182
|
if (r.ok) {
|
|
183
183
|
const j = await r.json();
|
|
184
|
-
|
|
184
|
+
if (j.status === "ended") {
|
|
185
|
+
// Server idle-ended this session. Drop it and create a fresh one on next mount cycle.
|
|
186
|
+
try { await AsyncStorage.removeItem(STORAGE_KEY); } catch {}
|
|
187
|
+
setSessionId(null);
|
|
188
|
+
setIngestToken(null);
|
|
189
|
+
setStatus("idle");
|
|
190
|
+
} else {
|
|
191
|
+
setStatus(j.status);
|
|
192
|
+
}
|
|
185
193
|
}
|
|
186
194
|
} catch {}
|
|
187
195
|
}, 5000);
|
|
@@ -291,9 +299,21 @@ export function SessionReplayProvider({ config, children }: { config: Config; ch
|
|
|
291
299
|
case "guided_tour_request":
|
|
292
300
|
setTourState({ phase: "consent", agentName: msg.agentName });
|
|
293
301
|
break;
|
|
294
|
-
case "guided_tour_pointer":
|
|
295
|
-
|
|
302
|
+
case "guided_tour_pointer": {
|
|
303
|
+
// Prefer normalized coords (nx/ny in 0..1) and multiply by view size in points.
|
|
304
|
+
// Falls back to raw x/y for older dashboards.
|
|
305
|
+
const dim = viewSizeRef.current;
|
|
306
|
+
let px: number; let py: number;
|
|
307
|
+
if (typeof msg.nx === "number" && typeof msg.ny === "number" && dim.w > 0 && dim.h > 0) {
|
|
308
|
+
px = msg.nx * dim.w;
|
|
309
|
+
py = msg.ny * dim.h;
|
|
310
|
+
} else {
|
|
311
|
+
px = msg.x ?? 0;
|
|
312
|
+
py = msg.y ?? 0;
|
|
313
|
+
}
|
|
314
|
+
setPointer({ x: px, y: py, t: Date.now() });
|
|
296
315
|
break;
|
|
316
|
+
}
|
|
297
317
|
case "guided_tour_end":
|
|
298
318
|
setTourState({ phase: "idle" });
|
|
299
319
|
setPointer(null);
|
|
@@ -312,6 +332,8 @@ export function SessionReplayProvider({ config, children }: { config: Config; ch
|
|
|
312
332
|
// ---- Guided tour state ----
|
|
313
333
|
const [tourState, setTourState] = useState<{ phase: "idle" | "consent" | "active"; agentName?: string }>({ phase: "idle" });
|
|
314
334
|
const [pointer, setPointer] = useState<{ x: number; y: number; t: number } | null>(null);
|
|
335
|
+
// Captured root-View size (in points) so we can place the agent pointer correctly.
|
|
336
|
+
const viewSizeRef = useRef<{ w: number; h: number }>({ w: 0, h: 0 });
|
|
315
337
|
|
|
316
338
|
function consentTour(accept: boolean) {
|
|
317
339
|
if (accept) {
|
|
@@ -352,6 +374,10 @@ export function SessionReplayProvider({ config, children }: { config: Config; ch
|
|
|
352
374
|
ref={rootRef}
|
|
353
375
|
collapsable={false}
|
|
354
376
|
style={{ flex: 1 }}
|
|
377
|
+
onLayout={(e) => {
|
|
378
|
+
const { width, height } = e.nativeEvent.layout;
|
|
379
|
+
viewSizeRef.current = { w: width, h: height };
|
|
380
|
+
}}
|
|
355
381
|
{...panResponder.panHandlers}
|
|
356
382
|
onTouchEndCapture={onTouchEndCapture}
|
|
357
383
|
>
|
|
@@ -389,7 +415,10 @@ export function SessionReplayProvider({ config, children }: { config: Config; ch
|
|
|
389
415
|
<Text onPress={endTourLocal} style={styles.endTourText}>End Tour</Text>
|
|
390
416
|
</View>
|
|
391
417
|
{pointer && (
|
|
392
|
-
|
|
418
|
+
<>
|
|
419
|
+
<View pointerEvents="none" style={[styles.pointerHalo, { left: pointer.x - 24, top: pointer.y - 24 }]} />
|
|
420
|
+
<View pointerEvents="none" style={[styles.pointer, { left: pointer.x - 12, top: pointer.y - 12 }]} />
|
|
421
|
+
</>
|
|
393
422
|
)}
|
|
394
423
|
</>
|
|
395
424
|
)}
|
|
@@ -441,8 +470,13 @@ const styles = StyleSheet.create({
|
|
|
441
470
|
borderRadius: 999, fontSize: 13, fontWeight: "600", overflow: "hidden",
|
|
442
471
|
},
|
|
443
472
|
pointer: {
|
|
444
|
-
position: "absolute", width:
|
|
445
|
-
backgroundColor: "rgba(239,68,68,
|
|
446
|
-
shadowColor: "#ef4444", shadowOpacity: 0.
|
|
473
|
+
position: "absolute", width: 24, height: 24, borderRadius: 12,
|
|
474
|
+
backgroundColor: "rgba(239,68,68,1)", borderWidth: 3, borderColor: "#fff",
|
|
475
|
+
shadowColor: "#ef4444", shadowOpacity: 0.9, shadowRadius: 8,
|
|
476
|
+
elevation: 12, zIndex: 10000,
|
|
477
|
+
},
|
|
478
|
+
pointerHalo: {
|
|
479
|
+
position: "absolute", width: 48, height: 48, borderRadius: 24,
|
|
480
|
+
backgroundColor: "rgba(239,68,68,0.25)", zIndex: 9999,
|
|
447
481
|
},
|
|
448
482
|
});
|