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