React Native

Full native screen capture on Android and iOS. Touch trails on Android (iOS on the roadmap). Session resumption across backgrounding. Works with Expo dev clients and bare React Native 0.76+.

Install

# Expo
npx expo install @galacha/react-native
 
# Bare RN
npm install @galacha/react-native
cd ios && pod install

Rebuild the native binary

This package ships Kotlin and Swift. After installing, a JS reload will not pick up the native module . you have to rebuild the dev client:

# Expo managed
npx expo prebuild
npx expo run:android
npx expo run:ios
 
# EAS
eas build --profile development --platform android
eas build --profile development --platform ios

First build is slow (3–8 minutes on a cold cache). Subsequent builds are incremental.

Initialize

The SDK is a default export. Import it once, call .init() inside a mount effect, and wrap your app tree with <TouchCaptureView> so touch events have a place to land.

// app/_layout.tsx  (Expo Router)
import { useEffect } from "react";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import Galacha, { TouchCaptureView } from "@galacha/react-native";
 
export default function RootLayout() {
  useEffect(() => {
    Galacha.init({
      projectKey: process.env.EXPO_PUBLIC_GALACHA_PROJECT_KEY!,
    });
  }, []);
 
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <TouchCaptureView buffer={Galacha.getBuffer()!}>
        {/* rest of your providers + navigation */}
      </TouchCaptureView>
    </GestureHandlerRootView>
  );
}

Env var:

# .env
EXPO_PUBLIC_GALACHA_PROJECT_KEY=your_project_key_from_the_dashboard

The project key is the long hex string visible on your project page in the dashboard. It's not the same as the display name or the project UUID . it's the token the SDK authenticates with.

Identify the user

Two positional arguments: a string userId and an optional traits object.

// After login
useEffect(() => {
  if (!user) return;
  Galacha.identify(String(user.id), {
    email: user.email,
    name: `${user.first_name} ${user.last_name}`,
    phone: user.phone_number,
  });
}, [user]);

All future events in this and subsequent sessions are tied to the user.

Capture tuning

All defaults shown . change anything you need. Defaults are sized for ~3-minute sessions at ~300KB.

Galacha.init({
  projectKey: "...",
 
  screenCaptureFps: 1,         // frames per second
  screenCaptureQuality: 0.4,   // JPEG quality, 0–1
  screenCaptureScale: 0.35,    // frame downscale factor
 
  captureTouches: true,
  captureNetwork: true,
  captureErrors: true,
  maskTextInputs: true,
 
  flushIntervalMs: 5000,
  flushMaxEvents: 50,
  sessionTimeoutMs: 30 * 60 * 1000,  // resume-same-session window
});

Raising screenCaptureFps or screenCaptureQuality directly increases session size on the wire and in storage.

Privacy . mask specific views

Wrap anything you don't want captured. The view is rendered as a solid block in the replay frames:

import { GalachaPrivate } from "@galacha/react-native";
 
<GalachaPrivate>
  <CreditCardForm />
</GalachaPrivate>

Privacy . full mode

Use on sensitive screens. Every frame is fully masked until you turn it off:

import { useEffect } from "react";
import Galacha from "@galacha/react-native";
 
function CheckoutScreen() {
  useEffect(() => {
    Galacha.setPrivacyMode(true);
    return () => Galacha.setPrivacyMode(false);
  }, []);
  // ...
}

Touch trails (Android, v0.8.0+)

Touch events are throttled natively to ~30Hz. Swipes and scrolls appear in the replay player as fading purple strokes with direction arrows on release.

No configuration . it's on by default as long as <TouchCaptureView> wraps your app. iOS touch trails are on the roadmap; iOS still captures screen frames.

To disable entirely:

Galacha.init({
  projectKey: "...",
  captureTouches: false,
});

Custom events

Galacha.track("add_to_cart", { sku: "ABC-123", price: 49_990 });

Names are capped at 200 characters. Properties must be JSON-serializable.

Manual lifecycle

Most apps never need these, but they exist:

MethodPurpose
Galacha.flush()Force-flush the buffer to the API right now
Galacha.stop()Stop recording and tear down listeners
Galacha.getSessionId()Current session ID, or null
Galacha.getVisitorId()Persistent anonymous visitor ID
Galacha.isReady()true if init() has completed

Common pitfalls

ProblemFix
Expo Go does nothingThis SDK has native modules. Use a dev client (expo run:android or EAS build).
Upgraded SDK, new features missingYou forgot to rebuild the native binary. JS reloads don't pick up Kotlin/Swift changes.
TouchCaptureView throws about bufferCall init() before rendering the wrapper . or use Galacha.getBuffer()! after a mount effect.
Touch trails not showingYou're watching a session recorded before 0.8.0 was installed, or TouchCaptureView isn't in the tree.
Zombie session in the dashboardThe app crashed before the first metadata event flushed. Zombies are never charged.
Main-thread stutterDon't block JS with heavy sync work. Screen capture runs on a background thread already.