@exodus/react-native-screenshot-detector 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/android/build.gradle
CHANGED
package/index.ios.js
CHANGED
|
@@ -4,12 +4,59 @@ const { RNScreenshotDetector } = NativeModules
|
|
|
4
4
|
const eventEmitter = new NativeEventEmitter(RNScreenshotDetector)
|
|
5
5
|
|
|
6
6
|
const SCREENSHOT_EVENT = 'ScreenshotTaken'
|
|
7
|
+
const SCREEN_RECORDING_EVENT = 'ScreenRecordingChanged'
|
|
7
8
|
|
|
8
9
|
const subscribe = (cb) => {
|
|
9
|
-
const sub = eventEmitter.addListener(SCREENSHOT_EVENT,
|
|
10
|
-
|
|
10
|
+
const sub = eventEmitter.addListener(SCREENSHOT_EVENT, (data) => {
|
|
11
|
+
cb(data)
|
|
12
|
+
})
|
|
13
|
+
return () => {
|
|
14
|
+
sub.remove()
|
|
15
|
+
}
|
|
11
16
|
}
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
const subscribeToScreenRecording = (cb) => {
|
|
19
|
+
if (RNScreenshotDetector.subscribeToScreenRecording) {
|
|
20
|
+
RNScreenshotDetector.subscribeToScreenRecording()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const sub = eventEmitter.addListener(SCREEN_RECORDING_EVENT, (data) => {
|
|
24
|
+
cb(data)
|
|
25
|
+
})
|
|
26
|
+
return () => {
|
|
27
|
+
sub.remove()
|
|
28
|
+
if (RNScreenshotDetector.unsubscribeFromScreenRecording) {
|
|
29
|
+
RNScreenshotDetector.unsubscribeFromScreenRecording()
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const disableScreenshots = () => {
|
|
35
|
+
if (RNScreenshotDetector.disableScreenshots) {
|
|
36
|
+
RNScreenshotDetector.disableScreenshots()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const enableScreenshots = () => {
|
|
41
|
+
if (RNScreenshotDetector.enableScreenshots) {
|
|
42
|
+
RNScreenshotDetector.enableScreenshots()
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const isScreenRecording = async () => {
|
|
47
|
+
if (RNScreenshotDetector.isScreenRecording) {
|
|
48
|
+
return RNScreenshotDetector.isScreenRecording()
|
|
49
|
+
} else {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const ScreenshotDetector = {
|
|
14
55
|
subscribe,
|
|
56
|
+
subscribeToScreenRecording,
|
|
57
|
+
disableScreenshots,
|
|
58
|
+
enableScreenshots,
|
|
59
|
+
isScreenRecording,
|
|
15
60
|
}
|
|
61
|
+
|
|
62
|
+
export default ScreenshotDetector
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
@interface RNScreenshotDetector : RCTEventEmitter <RCTBridgeModule>
|
|
11
11
|
|
|
12
|
+
@property (nonatomic, strong) UITextField *secureTextField;
|
|
13
|
+
|
|
12
14
|
- (void)screenshotDetected:(NSNotification*)notification;
|
|
15
|
+
- (void)screenRecordingChanged:(NSNotification*)notification;
|
|
13
16
|
|
|
14
17
|
@end
|
|
@@ -6,40 +6,161 @@
|
|
|
6
6
|
|
|
7
7
|
#import "RNScreenshotDetector.h"
|
|
8
8
|
#import <React/RCTBridge.h>
|
|
9
|
+
#import <UIKit/UIKit.h>
|
|
9
10
|
|
|
10
11
|
@implementation RNScreenshotDetector
|
|
11
12
|
{
|
|
12
|
-
id
|
|
13
|
+
id screenshotObserver;
|
|
14
|
+
id screenRecordingObserver;
|
|
15
|
+
UIView *securityOverlay;
|
|
16
|
+
BOOL isProtectionEnabled;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
RCT_EXPORT_MODULE();
|
|
16
20
|
|
|
17
|
-
|
|
18
21
|
- (NSArray<NSString *> *)supportedEvents {
|
|
19
|
-
return @[@"ScreenshotTaken"];
|
|
22
|
+
return @[@"ScreenshotTaken", @"ScreenRecordingChanged"];
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
- (void)startObserving {
|
|
23
|
-
// Now set up handler to detect if user takes a screenshot
|
|
24
26
|
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
screenshotObserver = [[NSNotificationCenter defaultCenter]
|
|
28
|
+
addObserverForName:UIApplicationUserDidTakeScreenshotNotification
|
|
29
|
+
object:nil
|
|
30
|
+
queue:mainQueue
|
|
31
|
+
usingBlock:^(NSNotification *notification) {
|
|
32
|
+
[self screenshotDetected:notification];
|
|
33
|
+
}];
|
|
34
|
+
|
|
35
|
+
screenRecordingObserver = [[NSNotificationCenter defaultCenter]
|
|
36
|
+
addObserverForName:UIScreenCapturedDidChangeNotification
|
|
37
|
+
object:nil
|
|
38
|
+
queue:mainQueue
|
|
39
|
+
usingBlock:^(NSNotification *notification) {
|
|
40
|
+
[self screenRecordingChanged:notification];
|
|
41
|
+
}];
|
|
31
42
|
}
|
|
32
43
|
|
|
33
44
|
- (void)stopObserving {
|
|
34
|
-
if (
|
|
35
|
-
[[NSNotificationCenter defaultCenter] removeObserver:
|
|
45
|
+
if (screenshotObserver != nil) {
|
|
46
|
+
[[NSNotificationCenter defaultCenter] removeObserver:screenshotObserver];
|
|
47
|
+
screenshotObserver = nil;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (screenRecordingObserver != nil) {
|
|
51
|
+
[[NSNotificationCenter defaultCenter] removeObserver:screenRecordingObserver];
|
|
52
|
+
screenRecordingObserver = nil;
|
|
36
53
|
}
|
|
37
54
|
}
|
|
38
55
|
|
|
39
56
|
- (void)screenshotDetected:(NSNotification *)notification {
|
|
40
|
-
if (
|
|
57
|
+
if (screenshotObserver != nil) {
|
|
41
58
|
[self sendEventWithName:@"ScreenshotTaken" body:@{}];
|
|
42
59
|
}
|
|
43
60
|
}
|
|
44
61
|
|
|
45
|
-
|
|
62
|
+
- (void)screenRecordingChanged:(NSNotification *)notification {
|
|
63
|
+
if (screenRecordingObserver != nil) {
|
|
64
|
+
BOOL isRecording = [UIScreen mainScreen].isCaptured;
|
|
65
|
+
|
|
66
|
+
[self sendEventWithName:@"ScreenRecordingChanged" body:@{@"isRecording": @(isRecording)}];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
RCT_EXPORT_METHOD(disableScreenshots) {
|
|
71
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
72
|
+
isProtectionEnabled = YES;
|
|
73
|
+
[self enableTrueScreenshotPrevention];
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
RCT_EXPORT_METHOD(enableScreenshots) {
|
|
78
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
79
|
+
isProtectionEnabled = NO;
|
|
80
|
+
[self disableTrueScreenshotPrevention];
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
RCT_EXPORT_METHOD(isScreenRecording:(RCTPromiseResolveBlock)resolve
|
|
85
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
86
|
+
BOOL isRecording = [UIScreen mainScreen].isCaptured;
|
|
87
|
+
resolve(@(isRecording));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
RCT_EXPORT_METHOD(subscribeToScreenRecording) {
|
|
91
|
+
[self startObserving];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
RCT_EXPORT_METHOD(unsubscribeFromScreenRecording) {
|
|
95
|
+
[self stopObserving];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Screenshot Prevention using Secure Text Field
|
|
99
|
+
- (void)enableTrueScreenshotPrevention {
|
|
100
|
+
if (self.secureTextField == nil) {
|
|
101
|
+
self.secureTextField = [[UITextField alloc] init];
|
|
102
|
+
self.secureTextField.userInteractionEnabled = NO;
|
|
103
|
+
self.secureTextField.secureTextEntry = YES;
|
|
104
|
+
|
|
105
|
+
UIWindow *keyWindow = [self getKeyWindow];
|
|
106
|
+
if (keyWindow != nil) {
|
|
107
|
+
[keyWindow makeKeyAndVisible];
|
|
108
|
+
|
|
109
|
+
// Make the app window a sublayer of the secure text field
|
|
110
|
+
[keyWindow.layer.superlayer addSublayer:self.secureTextField.layer];
|
|
111
|
+
|
|
112
|
+
// Add the window layer as a sublayer of the secure text field's first sublayer
|
|
113
|
+
NSArray *sublayers = self.secureTextField.layer.sublayers;
|
|
114
|
+
if (sublayers.count > 0) {
|
|
115
|
+
[sublayers.firstObject addSublayer:keyWindow.layer];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
self.secureTextField.secureTextEntry = YES;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
- (void)disableTrueScreenshotPrevention {
|
|
124
|
+
if (self.secureTextField != nil) {
|
|
125
|
+
self.secureTextField.secureTextEntry = NO;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
- (UIWindow *)getKeyWindow {
|
|
130
|
+
UIWindow *keyWindow = nil;
|
|
131
|
+
|
|
132
|
+
NSSet<UIScene *> *connectedScenes = [UIApplication sharedApplication].connectedScenes;
|
|
133
|
+
for (UIScene *scene in connectedScenes) {
|
|
134
|
+
if ([scene isKindOfClass:[UIWindowScene class]]) {
|
|
135
|
+
UIWindowScene *windowScene = (UIWindowScene *)scene;
|
|
136
|
+
for (UIWindow *window in windowScene.windows) {
|
|
137
|
+
if (window.isKeyWindow) {
|
|
138
|
+
keyWindow = window;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (keyWindow) break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (keyWindow == nil) {
|
|
147
|
+
keyWindow = [UIApplication sharedApplication].keyWindow;
|
|
148
|
+
if (keyWindow == nil) {
|
|
149
|
+
for (UIWindow *window in [UIApplication sharedApplication].windows) {
|
|
150
|
+
if (window.isKeyWindow) {
|
|
151
|
+
keyWindow = window;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return keyWindow;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
- (void)dealloc {
|
|
162
|
+
[self stopObserving];
|
|
163
|
+
[self disableTrueScreenshotPrevention];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@end
|