hint.dev
v1.8.1
Published
React Native SDK for session recording, replay, and remote control
Maintainers
Readme
hint.dev
React Native SDK for session recording, replay, remote control, and guided support tours.
Installation
npm install hint.dev \
react-native-view-shot @react-native-async-storage/async-storageFor Expo:
npx expo install hint.dev \
react-native-view-shot @react-native-async-storage/async-storage
# Requires a development build (Expo Go does not support the native
# screenshot / remote-control modules).
npx expo prebuild && npx expo run:ios # or run:androidUpgrading on Android
This SDK ships an autolinked native Android module (PixelCopy) that captures
views with elevation/shadows/MapView correctly — react-native-view-shot
silently drops those on Android. After upgrading, you must rebuild the
Android app for autolinking to pick it up:
cd android && ./gradlew clean && cd ..
npx react-native run-android
# Expo bare / development build:
npx expo run:android --no-build-cacheRequires Android 7.0+ (API 24). On older devices and on iOS, capture
automatically falls back to react-native-view-shot.
Known limitation: PixelCopy captures the activity's window only, so
React Native Modal, react-native-modal, react-native-paper Dialog,
and bottom-sheet libraries that use the Modal API render in a separate
Window and will not appear in screenshots. @gorhom/bottom-sheet
renders inline in the React tree and is captured normally. If PixelCopy ever
throws on a device, the SDK logs a warning and silently falls back to
view-shot for the rest of the session.
Quick Start (recommended)
Drop a single <SessionReplayProvider> at your app root. It wires up:
SessionReplay.init(config)- screenshot capture (using
react-native-view-shot) - wireframe root attach
- gesture (tap / swipe) capture
- the guided-tour overlay (consent prompt + edge halo + laser pointer + End Tour pill)
- the remote-control overlay (visualizes incoming taps)
- iOS keyboard auto-pause for screenshot capture (avoids password-field flicker)
import { SessionReplayProvider } from 'hint.dev';
import SessionReplay from 'hint.dev';
import { NavigationContainer } from '@react-navigation/native';
export default function App() {
return (
<SessionReplayProvider
config={{
serverUrl: 'https://smartlook.replit.app/api',
appId: 'app_XXXXXXXXXXXX',
teamId: 'team_XXXXXXXXXXXX', // optional but recommended — server verifies it matches the app
user: { userId: 'user_123', userName: 'Jane Smith' },
enableScreenshots: true,
enableRemoteControl: true,
enableWireframeRecording: true,
}}
onReady={(sessionId) => console.log('session', sessionId)}
>
<NavigationContainer
onStateChange={(state) => {
const route = state?.routes[state.index];
if (route) SessionReplay.trackScreen(route.name);
}}
>
{/* your screens */}
</NavigationContainer>
</SessionReplayProvider>
);
}Important — Do NOT double-mount the guided tour
SessionReplayProvider already mounts <GuidedTourOverlay /> for you.
Wrapping additionally with <GuidedTourProvider> or rendering
<GuidedTourOverlay /> manually creates two overlays stacked on top of
each other — the user has to click Allow / End Tour twice
(once per overlay).
SDK 1.7.12+ guards against this with a singleton check: extra instances silently no-op and log a warning in dev mode. Easiest fix: remove the extra wrapper.
To opt out of the auto-mount and place the overlay yourself:
<SessionReplayProvider config={...} enableGuidedTour={false}>
...
</SessionReplayProvider>Customizing the consent prompts
Pass renderers directly to the provider:
<SessionReplayProvider
config={...}
renderGuidedTourConsent={({ agentName, onAccept, onDecline }) => (
<MySheet title={`${agentName ?? 'Support'} wants to guide you`}
onAccept={onAccept} onDecline={onDecline} />
)}
renderGuidedTourVoiceConsent={({ onAccept, onDecline }) => (
<MyVoicePrompt onAccept={onAccept} onDecline={onDecline} />
)}
>
...
</SessionReplayProvider>Note: the built-in consent prompt is rendered as an inline absolutely-
positioned View, not a native <Modal>. RN Modals leave a stale
native window behind for several frames after dismiss, which silently
swallows touches in the overlay that mounts next (e.g. the End Tour pill).
Privacy
SessionReplay.pause(); // ... sensitive operation ... SessionReplay.resume();
<SessionReplay.PrivacyLayer redact>
<CreditCardForm />
</SessionReplay.PrivacyLayer>While the iOS keyboard is up, screenshot capture is paused automatically to avoid UIKit's secure-text-entry redaction (which would otherwise make password fields flicker every time a frame is captured).
API
SessionReplay.init(config)— initialize and start recordingSessionReplay.trackScreen(name)— track screen navigationSessionReplay.trackEvent(name, data)— log custom eventsSessionReplay.pause()/SessionReplay.resume()— pause/resume recordingSessionReplay.stop()— end the sessionSessionReplay.startLiveSession()/endLiveSession()— toggle live streamingSessionReplay.onRemoteCommand(handler)— listen for remote commandsSessionReplay.setScreenshotCapture(fn)— manual override (Provider does this for you)SessionReplay.setCaptureSuppressed(bool)— manual capture pause toggleSessionReplay.attachRoot(ref)— manual wireframe root (Provider does this for you)
Data retention
All session data, events, and screenshots are deleted 48 hours after session creation. Cleanup runs hourly on the server.
License
MIT
