react-native-screen-security
v1.2.1
Published
A high-performance React Native TurboModule providing native screen security, anti-screenshot protection, and seamless background blurring for iOS and Android.
Downloads
921
Maintainers
Readme
react-native-screen-security
High-performance UI security and Data Leakage Prevention (DLP) for React Native New Architecture (Fabric & TurboModules). Prevent screenshots, mask sensitive content in the OS App Switcher, and detect screen capture events — with zero Bridge overhead.
Key Features
- 🚫 Screen Capture Prevention — Leverages Android's
FLAG_SECUREto block screenshots and screen recordings globally or per-screen. - 👁️ App Switcher Masking (iOS) — Protects user privacy by automatically blurring or hiding sensitive views (like credit cards or balances) when the app goes into the background.
- 🔔 Screenshot Detection — Provides clean, reactive hooks to notify your app whenever a screenshot is taken.
Table of contents
- Features
- See it in action
- Requirements
- Installation
- Usage
- API reference
- Platform behavior
- Permissions
- Testing
- Limitations
- Troubleshooting
- License
Features
- Secure window — blocks screen recording and screenshots
- iOS: secure canvas layer overlay (renders black/blank during capture)
- Android:
FLAG_SECUREon the current Activity window
- App switcher blur (iOS) — blurs the window when the app goes to background
- SecureView component — per-region GPU blanking on iOS; fragment-scoped
FLAG_SECUREon Android (tab-safe) - Screenshot detection — emits
onScreenshotTakenevents to JavaScript - Screen recording detection (iOS) —
useScreenRecordingDetectionhook - React hooks —
useScreenSecurity,useScreenshotDetection - Autolinking — works out of the box with React Native autolinking
See it in action
iOS — SecureView component masking
Wrap sensitive UI in <SecureView> for per-region GPU blanking. The live app stays fully readable; captured screenshots and recordings mask only the protected component.
Android — screenshot & recording blocked (FLAG_SECURE)
Window or fragment-scoped FLAG_SECURE blocks screenshots and screen recordings. Android shows a system notification when capture is denied.
App switcher privacy
When the app enters the background, iOS blurs the window in the task switcher (setAppSwitcherBlur / useScreenSecurity). Android hides the recents preview while FLAG_SECURE is active.
Requirements
| | Version |
| ------------ | ----------------------------------------- |
| React Native | >= 0.79 (New Architecture required) |
| iOS | >= 13.4 |
| Android | minSdk 24, compileSdk 34+ recommended |
| React | >= 18 |
Android tab navigation: component-level protection requires
<SecureView>(or native fragment host via react-native-screens). The hook alone enables iOS app-switcher blur but does not applyFLAG_SECUREon Android incomponentmode.
Installation
yarn add react-native-screen-security
# or
npm install react-native-screen-securityiOS — install pods and rebuild:
cd ios && pod install && cd ..
yarn iosAndroid — rebuild (autolinking handles the rest):
yarn androidMetro hot reload is not enough after installing a native module. You must rebuild the app.
Local / monorepo setup
Yarn workspace (this repo):
{
"workspaces": { "packages": ["example"] },
"dependencies": {
"react-native-screen-security": "*"
}
}From the repo root: yarn install → yarn workspace screen-security-example pods (iOS) → yarn workspace screen-security-example ios / android.
File dependency in another app:
{
"dependencies": {
"react-native-screen-security": "file:../react-native-screen-security"
}
}Then: yarn install → cd ios && pod install → rebuild.
TypeScript in monorepos: the published
typesfield points atlib/index.d.ts, which is generated and gitignored. After cloning, runyarn buildat the library root, or map the example app to source (seeexample/tsconfig.jsonpaths). Metro already bundlessrc/index.tsvia the package"react-native"field.
Example app
The example/ app exercises every API (tabs, global/component protection, detection, blur, toggles, imperative calls, getSecurityState). Use it for manual QA when changing native code.
Choosing an API
Use this decision tree to pick the right integration:
| Your goal | Use |
| ------------------------------------------------------ | ----------------------------------------------------------------------------------- |
| Protect one screen in a tab navigator (Android) | <SecureView> (or <SecureWindowAnchor /> at screen root) |
| GPU-blank a region on iOS + app-switcher blur | useScreenSecurity() + <SecureView> around sensitive content |
| Protect the entire app after login | useScreenSecurity({ protectionLevel: 'global' }) at app root |
| Fragment-scoped Android protection without wrapping UI | <SecureWindowAnchor /> |
| Toggle protection from settings | useScreenSecurity({ enabled: isOn }) or imperative API (see ref-count note below) |
Android tabs: component-level protection works best with react-native-screens native fragments. The library binds FLAG_SECURE to Fragment resume/pause — no hard peer dependency, but tabs without native screen fragments may not behave correctly.
Ref-count semantics
setSecureWindow(true) and setSecureWindow(false) are +1 / −1 deltas, not absolute on/off toggles. The same applies to enableFullProtection() / disableFullProtection() and useScreenSecurity({ protectionLevel: 'global' }).
- Mount two global hooks → native ref count is 2; unmounting one leaves protection active.
- Call
enableFullProtection()twice → calldisableFullProtection()twice to fully release.
On iOS, setAppSwitcherBlur is also ref-counted. When multiple hooks enable blur with different blurStyle values, the last caller's style applies while the ref count stays above zero.
Usage
Step 1: Import
import {
useScreenSecurity,
useScreenshotDetection,
useScreenRecordingDetection,
SecureView,
SecureWindowAnchor,
onScreenRecordingStatusChanged,
enableFullProtection,
disableFullProtection,
} from 'react-native-screen-security';Step 2: Protect a screen (recommended)
Use useScreenSecurity() for iOS app-switcher blur and wrap sensitive content in <SecureView>.
On Android, SecureView binds FLAG_SECURE to the native Fragment lifecycle (works correctly with tab navigators). On iOS, SecureView GPU-blanks only the wrapped region during capture.
import React from 'react';
import { Text } from 'react-native';
import { useScreenSecurity, SecureView } from 'react-native-screen-security';
export function PaymentDetailsScreen() {
useScreenSecurity();
return (
<SecureView style={{ flex: 1 }}>
<Text>Card number: •••• •••• •••• 4242</Text>
</SecureView>
);
}App-wide protection (both platforms):
useScreenSecurity({ protectionLevel: 'global' });With blur style (iOS app switcher):
useScreenSecurity({ blur: true, blurStyle: 'dark' });
// blurStyle options: 'light' | 'dark' | 'system' | 'extraLight' (default: 'system')See App switcher privacy for a visual preview on iOS and Android.
Secure window only (no app switcher blur on iOS):
useScreenSecurity({ blur: false });Step 3: Detect screenshots (optional)
React to screenshot attempts — show a warning, log analytics, invalidate a session, etc.
import React from 'react';
import { Alert } from 'react-native';
import { useScreenSecurity, useScreenshotDetection } from 'react-native-screen-security';
export function ConfidentialDocumentScreen() {
useScreenSecurity();
useScreenshotDetection(() => {
Alert.alert('Screenshot blocked', 'Screenshots are not allowed on this screen.');
});
return (
<SecureView style={{ flex: 1 }}>
<DocumentViewer />
</SecureView>
);
}Step 4: Detect screen recording (iOS, optional)
import { useScreenRecordingDetection } from 'react-native-screen-security';
useScreenRecordingDetection(({ isCaptured }) => {
console.log('Screen recording:', isCaptured ? 'started' : 'stopped');
});Common patterns
Pattern 1: Protect specific screens in a navigator
// screens/BankAccountScreen.tsx
import { useScreenSecurity, SecureView } from 'react-native-screen-security';
export function BankAccountScreen() {
useScreenSecurity();
return (
<SecureView style={{ flex: 1 }}>
<AccountDetails />
</SecureView>
);
}
// screens/SettingsScreen.tsx — no hook, screen is not protected
export function SettingsScreen() {
return <SettingsList />;
}Pattern 2: Protect the entire app after login
// App.tsx or AuthenticatedNavigator.tsx
import { useEffect } from 'react';
import { enableFullProtection, disableFullProtection } from 'react-native-screen-security';
export function AuthenticatedApp() {
useEffect(() => {
enableFullProtection('system');
return () => disableFullProtection();
}, []);
return <MainNavigator />;
}Pattern 3: Toggle protection from app settings
import { useEffect } from 'react';
import { enableFullProtection, disableFullProtection } from 'react-native-screen-security';
function AppRoot() {
const isSecurityEnabled = useAppSelector(state => state.settings.screenSecurity);
useEffect(() => {
if (isSecurityEnabled) {
enableFullProtection('system');
} else {
disableFullProtection();
}
}, [isSecurityEnabled]);
return <Navigator />;
}Pattern 4: Screenshot detection with analytics
import { useScreenSecurity, useScreenshotDetection } from 'react-native-screen-security';
function PiiScreen() {
useScreenSecurity();
useScreenshotDetection(() => {
analytics.track('screenshot_taken', { screen: 'pii_details' });
});
return <PiiForm />;
}Pattern 5: Manual subscription (outside React components)
import { onScreenshotTaken } from 'react-native-screen-security';
// In a service or singleton
const subscription = onScreenshotTaken(() => {
securityService.handleScreenshot();
});
// When done:
subscription.remove();Imperative API
Use these when you need control outside of React hooks (services, Redux middleware, auth flows).
import {
setSecureWindow,
setAppSwitcherBlur,
enableFullProtection,
disableFullProtection,
getSecurityState,
acquireSecureWindow,
onScreenshotTaken,
} from 'react-native-screen-security';
// Turn on everything (secure window + iOS app switcher blur)
enableFullProtection('system');
// Turn off everything
disableFullProtection();
// Granular control
setSecureWindow(true); // block capture/screenshots
setAppSwitcherBlur(true, 'dark'); // iOS only — blur in app switcher
setSecureWindow(false);
setAppSwitcherBlur(false, 'system');
// Sync native ref-count state (debug / settings UI)
const { secureWindowActive, appSwitcherBlurActive } = getSecurityState();
// Token-style release (services, navigation guards)
const token = acquireSecureWindow();
token.release();| Function | When to use |
| --------------------------------- | ------------------------------------------------------------- |
| useScreenSecurity() | Per screen — iOS blur; use with <SecureView> on Android |
| SecureView | Wrap sensitive UI — required for Android component protection |
| enableFullProtection() | App-wide — call in useEffect or after auth |
| setSecureWindow(true/false) | Fine-grained global window control |
| getSecurityState() | Read whether secure window / app-switcher blur refs are active |
| acquireSecureWindow() | Imperative global protection with release() |
| acquireAppSwitcherBlur() | iOS app-switcher blur with release() |
| useScreenshotDetection(cb) | React component — react to screenshots |
| useScreenRecordingDetection(cb) | iOS only — screen recording status |
| onScreenshotTaken(cb) | Non-React code — returns subscription to .remove() |
API reference
Hooks
useScreenSecurity(options?)
| Option | Type | Default | Description |
| ----------------- | ------------------------- | ------------- | --------------------------------------------------------- |
| protectionLevel | 'component' \| 'global' | 'component' | global applies window-wide protection on both platforms |
| blur | boolean | true | Enable app switcher blur on iOS |
| blurStyle | BlurStyle | 'system' | 'light', 'dark', 'system', or 'extraLight' |
| enabled | boolean | true | When false, the hook is a no-op |
useScreenshotDetection(callback: () => void)
Subscribes to onScreenshotTaken. Safe to pass inline callbacks — uses a stable ref internally.
useScreenRecordingDetection(callback: (event: { isCaptured: boolean }) => void)
Subscribes to onScreenRecordingStatusChanged. iOS only.
SecureView
Cross-platform container for sensitive content. Required on Android for component-level FLAG_SECURE (including tab navigators). On iOS, only wrapped regions are GPU-blanked during capture — see See it in action.
| Prop | Type | Default | Description |
| --------- | --------- | ------- | ------------------------------------------------------------------- |
| fill | boolean | false | When true, applies flex: 1 (pre-1.1.0 / 1.0.x default behavior) |
| enabled | boolean | true | When false, renders a plain View without native protection |
Does not apply flex: 1 by default — pass fill or style={{ flex: 1 }} for full-screen wrappers.
SecureWindowAnchor
Android-only invisible anchor that binds FLAG_SECURE to the hosting native Fragment. Use at the screen root when you need fragment-scoped protection without wrapping content in SecureView. Renders null on iOS.
Functions
| Function | Signature | Description |
| -------------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------- |
| setSecureWindow | (enable: boolean) => void | Ref-count +1/−1 for global secure window |
| setAppSwitcherBlur | (enable: boolean, style?: BlurStyle) => void | Ref-count +1/−1 for iOS app switcher blur |
| getSecurityState | () => SecurityState | Sync snapshot of native ref-count flags |
| acquireSecureWindow | () => SecurityToken | Enable global window; call release() |
| acquireAppSwitcherBlur | (style?: BlurStyle) => SecurityToken | iOS blur token with release() |
| enableFullProtection | (blurStyle?: BlurStyle) => void | Secure window + iOS blur |
| disableFullProtection | () => void | Disable all protection |
| onScreenshotTaken | (callback: () => void) => EmitterSubscription | Subscribe to screenshot events |
| onScreenRecordingStatusChanged | (callback: (event: { isCaptured: boolean }) => void) => EmitterSubscription | Subscribe to recording status (iOS only) |
Types
type BlurStyle = 'light' | 'dark' | 'system' | 'extraLight';
type ProtectionLevel = 'component' | 'global';
type ScreenRecordingEvent = { isCaptured: boolean };
type ScreenshotEvent = Record<string, never>;
type SecurityState = {
secureWindowActive: boolean;
appSwitcherBlurActive: boolean;
};
type SecurityToken = { release: () => void };
interface ScreenSecurityOptions {
protectionLevel?: ProtectionLevel;
blur?: boolean;
blurStyle?: BlurStyle;
enabled?: boolean;
}Events
| Event | Payload | When fired | Platforms |
| -------------------------------- | ------------------------- | ------------------------------------------------ | ------------ |
| onScreenshotTaken | {} | User takes a screenshot or screen capture starts | iOS, Android |
| onScreenRecordingStatusChanged | { isCaptured: boolean } | Screen recording starts or stops | iOS only |
Platform behavior
| Feature | iOS | Android |
| -------------------------------------------------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| SecureView + component mode | GPU-blanks wrapped content during capture | FLAG_SECURE while hosting Fragment is resumed (tab-safe) |
| useScreenSecurity({ protectionLevel: 'global' }) | Window secure layer + optional blur (ref-counted) | Window FLAG_SECURE (ref-counted) |
| setSecureWindow(true) | Content renders black during recording/capture | Blocks screenshots, recording, and recents preview — see See it in action |
| setAppSwitcherBlur | Blur overlay in app switcher | No-op (FLAG_SECURE already masks recents) — see App switcher privacy |
| Screenshot detection | userDidTakeScreenshot + screen capture | ScreenCaptureCallback (API 34+) or ContentObserver fallback |
| Screen recording detection | UIScreen.capturedDidChangeNotification | Not available |
Permissions
iOS — no additional permissions required.
Android — FLAG_SECURE works without any permission. Screenshot detection on API < 34 may benefit from READ_MEDIA_IMAGES (API 33+) or READ_EXTERNAL_STORAGE (API < 33). API 34+ uses ScreenCaptureCallback and needs no storage permission.
Testing
Library unit tests
yarn testHook ref-count behavior and event payloads are covered with a mocked native module (no device required).
Example app (manual)
yarn install
yarn workspace screen-security-example pods # iOS, first time / native dep changes
yarn workspace screen-security-example ios # or androidManual device testing
| Feature | iOS Simulator | iOS Device | Android Emulator | | ---------------------- | :-----------: | :--------: | :--------------: | | App switcher blur | Yes | Yes | N/A | | Secure window blocking | No | Yes | Yes | | Screenshot detection | No | Yes | Yes (API 34+) |
iOS: use a real device for secure window and screenshot tests.
Android: use an API 34+ emulator image. Trigger screenshot via ADB:
adb shell input keyevent 120Limitations
- Android component mode requires
<SecureView>in the tree —useScreenSecurity()alone does not applyFLAG_SECURE. - Android
FLAG_SECUREis window-wide while a secured Fragment is active — not pixel-level regions. - iOS secure layer uses a private
UITextFieldcanvas view — test on new iOS versions before release. - Android screenshot detection on API < 34 is heuristic-based; some OEMs may not be detected.
setSecureWindowon Android requires an active Activity — queued automatically if called too early.- iOS screenshot events still fire even when the screenshot appears blank — expected for audit/logging.
Troubleshooting
FLAG_SECURE stays on when switching tabs (Android) — ensure sensitive screens use <SecureView> and non-sensitive tabs do not. Protection follows Fragment resume/pause, not React unmount.
setSecureWindow has no effect on Android — ensure the app is in the foreground. The module queues the request if called before Activity is ready.
Screenshot events not firing on iOS Simulator — not supported. Use a real device.
Events not received — subscribe before taking a screenshot. The native observer starts when the first listener is added.
Migration
1.2.0 → 1.2.1
- iOS New Architecture: fixes a launch crash when the library was linked with Fabric enabled (missing
RTNSecureWindowAnchorView). Upgrade if you sawRCTThirdPartyComponentsProvider/NSClassFromStringfailures at startup. No API changes.
1.1.x → 1.2.0
- Default export removed — use named imports only, e.g.
import { enableFullProtection } from 'react-native-screen-security' - New (optional):
getSecurityState(),acquireSecureWindow(),acquireAppSwitcherBlur() - Monorepo consumers: run
yarn buildat the library root after clone solib/index.d.tsexists, or point TypeScript atsrc/index.ts(seeexample/tsconfig.json)
1.0.x → 1.1.0
useScreenRecordingDetectioncallback now receives{ isCaptured: boolean }instead of a bare boolean<SecureView>no longer defaults toflex: 1— addstyle={{ flex: 1 }}or use<SecureView fill>- Optional:
<SecureView enabled={false}>to conditionally disable a region without unmounting
License
MIT — see LICENSE.
