1import { EventEmitter, Subscription, UnavailabilityError } from 'expo-modules-core'; 2import { useEffect } from 'react'; 3 4import ExpoScreenCapture from './ExpoScreenCapture'; 5 6const activeTags: Set<string> = new Set(); 7const emitter = new EventEmitter(ExpoScreenCapture); 8 9const onScreenshotEventName = 'onScreenshot'; 10 11// @needsAudit 12/** 13 * Returns whether the Screen Capture API is available on the current device. 14 * 15 * @returns A promise that resolves to a `boolean` indicating whether the Screen Capture API is available on the current 16 * device. Currently, this resolves to `true` on Android and iOS only. 17 */ 18export async function isAvailableAsync(): Promise<boolean> { 19 return !!ExpoScreenCapture.preventScreenCapture && !!ExpoScreenCapture.allowScreenCapture; 20} 21 22// @needsAudit 23/** 24 * Prevents screenshots and screen recordings until `allowScreenCaptureAsync` is called or the app is restarted. If you are 25 * already preventing screen capture, this method does nothing (unless you pass a new and unique `key`). 26 * 27 * > Please note that on iOS, this will only prevent screen recordings, and is only available on 28 * iOS 11 and newer. On older iOS versions, this method does nothing. 29 * 30 * @param key Optional. If provided, this will help prevent multiple instances of the `preventScreenCaptureAsync` 31 * and `allowScreenCaptureAsync` methods (and `usePreventScreenCapture` hook) from conflicting with each other. 32 * When using multiple keys, you'll have to re-allow each one in order to re-enable screen capturing. 33 * Defaults to `'default'`. 34 */ 35export async function preventScreenCaptureAsync(key: string = 'default'): Promise<void> { 36 if (!ExpoScreenCapture.preventScreenCapture) { 37 throw new UnavailabilityError('ScreenCapture', 'preventScreenCaptureAsync'); 38 } 39 40 if (!activeTags.has(key)) { 41 activeTags.add(key); 42 await ExpoScreenCapture.preventScreenCapture(); 43 } 44} 45 46// @needsAudit 47/** 48 * Re-allows the user to screen record or screenshot your app. If you haven't called 49 * `preventScreenCapture()` yet, this method does nothing. 50 * 51 * @param key This will prevent multiple instances of the `preventScreenCaptureAsync` and 52 * `allowScreenCaptureAsync` methods from conflicting with each other. If provided, the value must 53 * be the same as the key passed to `preventScreenCaptureAsync` in order to re-enable screen 54 * capturing. Defaults to 'default'. 55 */ 56export async function allowScreenCaptureAsync(key: string = 'default'): Promise<void> { 57 if (!ExpoScreenCapture.preventScreenCapture) { 58 throw new UnavailabilityError('ScreenCapture', 'allowScreenCaptureAsync'); 59 } 60 61 activeTags.delete(key); 62 if (activeTags.size === 0) { 63 await ExpoScreenCapture.allowScreenCapture(); 64 } 65} 66 67// @needsAudit 68/** 69 * A React hook to prevent screen capturing for as long as the owner component is mounted. 70 * 71 * @param key. If provided, this will prevent multiple instances of this hook or the 72 * `preventScreenCaptureAsync` and `allowScreenCaptureAsync` methods from conflicting with each other. 73 * This argument is useful if you have multiple active components using the `allowScreenCaptureAsync` 74 * hook. Defaults to `'default'`. 75 */ 76export function usePreventScreenCapture(key: string = 'default'): void { 77 useEffect(() => { 78 preventScreenCaptureAsync(key); 79 80 return () => { 81 allowScreenCaptureAsync(key); 82 }; 83 }, [key]); 84} 85 86// @needsAudit 87/** 88 * Adds a listener that will fire whenever the user takes a screenshot while the app is foregrounded. 89 * On Android, this method requires the `READ_EXTERNAL_STORAGE` permission. You can request this 90 * with [`MediaLibrary.requestPermissionsAsync()`](./media-library/#medialibraryrequestpermissionsasync). 91 * 92 * @param listener The function that will be executed when the user takes a screenshot. 93 * This function accepts no arguments. 94 * 95 * @return A `Subscription` object that you can use to unregister the listener, either by calling 96 * `remove()` or passing it to `removeScreenshotListener`. 97 */ 98export function addScreenshotListener(listener: () => void): Subscription { 99 return emitter.addListener<void>(onScreenshotEventName, listener); 100} 101 102// @needsAudit 103/** 104 * Removes the subscription you provide, so that you are no longer listening for screenshots. 105 * 106 * If you prefer, you can also call `remove()` on that `Subscription` object, for example: 107 * 108 * ```ts 109 * let mySubscription = addScreenshotListener(() => { 110 * console.log("You took a screenshot!"); 111 * }); 112 * ... 113 * mySubscription.remove(); 114 * // OR 115 * removeScreenshotListener(mySubscription); 116 * ``` 117 * 118 * @param subscription Subscription returned by `addScreenshotListener`. 119 */ 120export function removeScreenshotListener(subscription: Subscription) { 121 emitter.removeSubscription(subscription); 122} 123 124export { Subscription }; 125