@exodus/react-native-screenshot-detector 1.1.2 → 1.2.1
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/index.ios.js
CHANGED
|
@@ -1,15 +1,91 @@
|
|
|
1
1
|
import { NativeEventEmitter, NativeModules } from 'react-native'
|
|
2
2
|
|
|
3
3
|
const { RNScreenshotDetector } = NativeModules
|
|
4
|
+
|
|
5
|
+
console.log('[TEST] RNScreenshotDetector module loaded:', !!RNScreenshotDetector)
|
|
6
|
+
console.log('[TEST] Available methods:', Object.keys(RNScreenshotDetector || {}))
|
|
7
|
+
|
|
4
8
|
const eventEmitter = new NativeEventEmitter(RNScreenshotDetector)
|
|
5
9
|
|
|
6
10
|
const SCREENSHOT_EVENT = 'ScreenshotTaken'
|
|
11
|
+
const SCREEN_RECORDING_EVENT = 'ScreenRecordingChanged'
|
|
7
12
|
|
|
8
13
|
const subscribe = (cb) => {
|
|
9
|
-
|
|
10
|
-
|
|
14
|
+
console.log('[TEST] subscribe called - using NEW explicit method!')
|
|
15
|
+
|
|
16
|
+
// Use the more explicit method name internally
|
|
17
|
+
if (RNScreenshotDetector && RNScreenshotDetector.subscribeToScreenshotAndScreenRecording) {
|
|
18
|
+
console.log('[TEST] Calling subscribeToScreenshotAndScreenRecording')
|
|
19
|
+
RNScreenshotDetector.subscribeToScreenshotAndScreenRecording()
|
|
20
|
+
console.log('[TEST] SUCCESS: subscribeToScreenshotAndScreenRecording called')
|
|
21
|
+
} else {
|
|
22
|
+
console.log('[TEST] ERROR: subscribeToScreenshotAndScreenRecording not available!')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const sub = eventEmitter.addListener(SCREENSHOT_EVENT, (data) => {
|
|
26
|
+
console.log('[TEST] ScreenshotTaken event received:', data)
|
|
27
|
+
cb(data)
|
|
28
|
+
})
|
|
29
|
+
return () => {
|
|
30
|
+
console.log('[TEST] Unsubscribing from screenshot events')
|
|
31
|
+
sub.remove()
|
|
32
|
+
|
|
33
|
+
// Use the more explicit method name internally
|
|
34
|
+
if (RNScreenshotDetector && RNScreenshotDetector.unsubscribeFromScreenshotAndScreenRecording) {
|
|
35
|
+
console.log('[TEST] Calling unsubscribeFromScreenshotAndScreenRecording')
|
|
36
|
+
RNScreenshotDetector.unsubscribeFromScreenshotAndScreenRecording()
|
|
37
|
+
console.log('[TEST] SUCCESS: unsubscribeFromScreenshotAndScreenRecording called')
|
|
38
|
+
} else {
|
|
39
|
+
console.log('[TEST] ERROR: unsubscribeFromScreenshotAndScreenRecording not available!')
|
|
40
|
+
}
|
|
41
|
+
}
|
|
11
42
|
}
|
|
12
43
|
|
|
13
|
-
|
|
44
|
+
const disableScreenshots = () => {
|
|
45
|
+
console.log('[TEST] disableScreenshots called')
|
|
46
|
+
if (RNScreenshotDetector && RNScreenshotDetector.disableScreenshots) {
|
|
47
|
+
console.log('[TEST] Calling native disableScreenshots')
|
|
48
|
+
RNScreenshotDetector.disableScreenshots()
|
|
49
|
+
console.log('[TEST] SUCCESS: disableScreenshots called')
|
|
50
|
+
} else {
|
|
51
|
+
console.log('[TEST] ERROR: disableScreenshots not available!')
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const enableScreenshots = () => {
|
|
56
|
+
console.log('[TEST] enableScreenshots called')
|
|
57
|
+
if (RNScreenshotDetector && RNScreenshotDetector.enableScreenshots) {
|
|
58
|
+
console.log('[TEST] Calling native enableScreenshots')
|
|
59
|
+
RNScreenshotDetector.enableScreenshots()
|
|
60
|
+
console.log('[TEST] SUCCESS: enableScreenshots called')
|
|
61
|
+
} else {
|
|
62
|
+
console.log('[TEST] ERROR: enableScreenshots not available!')
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const isScreenRecording = async () => {
|
|
67
|
+
console.log('[TEST] isScreenRecording called')
|
|
68
|
+
if (RNScreenshotDetector && RNScreenshotDetector.isScreenRecording) {
|
|
69
|
+
try {
|
|
70
|
+
const result = await RNScreenshotDetector.isScreenRecording()
|
|
71
|
+
console.log('[TEST] isScreenRecording result:', result)
|
|
72
|
+
return result
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.log('[TEST] ERROR in isScreenRecording:', error)
|
|
75
|
+
return false
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
console.log('[TEST] ERROR: isScreenRecording not available!')
|
|
79
|
+
return false
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Main API - only expose what's actually used externally
|
|
84
|
+
const ScreenshotDetector = {
|
|
14
85
|
subscribe,
|
|
86
|
+
disableScreenshots,
|
|
87
|
+
enableScreenshots,
|
|
88
|
+
isScreenRecording,
|
|
15
89
|
}
|
|
90
|
+
|
|
91
|
+
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,211 @@
|
|
|
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
|
+
BOOL isProtectionEnabled;
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
RCT_EXPORT_MODULE();
|
|
16
19
|
|
|
17
|
-
|
|
18
20
|
- (NSArray<NSString *> *)supportedEvents {
|
|
19
|
-
|
|
21
|
+
NSLog(@"[TEST] supportedEvents called");
|
|
22
|
+
return @[@"ScreenshotTaken", @"ScreenRecordingChanged"];
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
- (void)startObserving {
|
|
23
|
-
|
|
26
|
+
NSLog(@"[TEST] startObserving called");
|
|
24
27
|
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
|
|
29
|
+
if (screenshotObserver == nil) {
|
|
30
|
+
screenshotObserver = [[NSNotificationCenter defaultCenter]
|
|
31
|
+
addObserverForName:UIApplicationUserDidTakeScreenshotNotification
|
|
32
|
+
object:nil
|
|
33
|
+
queue:mainQueue
|
|
34
|
+
usingBlock:^(NSNotification *notification) {
|
|
35
|
+
[self screenshotDetected:notification];
|
|
36
|
+
}];
|
|
37
|
+
NSLog(@"[TEST] Screenshot observer registered successfully");
|
|
38
|
+
} else {
|
|
39
|
+
NSLog(@"[TEST] Screenshot observer already exists");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (screenRecordingObserver == nil) {
|
|
43
|
+
screenRecordingObserver = [[NSNotificationCenter defaultCenter]
|
|
44
|
+
addObserverForName:UIScreenCapturedDidChangeNotification
|
|
45
|
+
object:nil
|
|
46
|
+
queue:mainQueue
|
|
47
|
+
usingBlock:^(NSNotification *notification) {
|
|
48
|
+
[self screenRecordingChanged:notification];
|
|
49
|
+
}];
|
|
50
|
+
NSLog(@"[TEST] Screen recording observer registered successfully");
|
|
51
|
+
} else {
|
|
52
|
+
NSLog(@"[TEST] Screen recording observer already exists");
|
|
53
|
+
}
|
|
31
54
|
}
|
|
32
55
|
|
|
33
56
|
- (void)stopObserving {
|
|
34
|
-
|
|
35
|
-
|
|
57
|
+
NSLog(@"[TEST] stopObserving called");
|
|
58
|
+
if (screenshotObserver != nil) {
|
|
59
|
+
[[NSNotificationCenter defaultCenter] removeObserver:screenshotObserver];
|
|
60
|
+
screenshotObserver = nil;
|
|
61
|
+
NSLog(@"[TEST] Screenshot observer removed successfully");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (screenRecordingObserver != nil) {
|
|
65
|
+
[[NSNotificationCenter defaultCenter] removeObserver:screenRecordingObserver];
|
|
66
|
+
screenRecordingObserver = nil;
|
|
67
|
+
NSLog(@"[TEST] Screen recording observer removed successfully");
|
|
36
68
|
}
|
|
37
69
|
}
|
|
38
70
|
|
|
39
71
|
- (void)screenshotDetected:(NSNotification *)notification {
|
|
40
|
-
|
|
72
|
+
NSLog(@"[TEST] 🚨 SCREENSHOT DETECTED! 🚨");
|
|
73
|
+
if (screenshotObserver != nil) {
|
|
74
|
+
NSLog(@"[TEST] Sending ScreenshotTaken event to JavaScript");
|
|
41
75
|
[self sendEventWithName:@"ScreenshotTaken" body:@{}];
|
|
76
|
+
NSLog(@"[TEST] ScreenshotTaken event sent successfully");
|
|
77
|
+
} else {
|
|
78
|
+
NSLog(@"[TEST] ERROR: Screenshot observer is nil, not sending event");
|
|
42
79
|
}
|
|
43
80
|
}
|
|
44
81
|
|
|
45
|
-
|
|
82
|
+
- (void)screenRecordingChanged:(NSNotification *)notification {
|
|
83
|
+
BOOL isRecording = [UIScreen mainScreen].isCaptured;
|
|
84
|
+
NSLog(@"[TEST] Screen recording changed: %@", isRecording ? @"STARTED" : @"STOPPED");
|
|
85
|
+
|
|
86
|
+
if (screenRecordingObserver != nil) {
|
|
87
|
+
NSLog(@"[TEST] Sending ScreenRecordingChanged event to JavaScript");
|
|
88
|
+
[self sendEventWithName:@"ScreenRecordingChanged" body:@{@"isRecording": @(isRecording)}];
|
|
89
|
+
NSLog(@"[TEST] ScreenRecordingChanged event sent successfully");
|
|
90
|
+
} else {
|
|
91
|
+
NSLog(@"[TEST] ERROR: Screen recording observer is nil, not sending event");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
RCT_EXPORT_METHOD(disableScreenshots) {
|
|
96
|
+
NSLog(@"[TEST] disableScreenshots called");
|
|
97
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
98
|
+
isProtectionEnabled = YES;
|
|
99
|
+
NSLog(@"[TEST] Protection enabled, calling enableTrueScreenshotPrevention");
|
|
100
|
+
[self enableTrueScreenshotPrevention];
|
|
101
|
+
NSLog(@"[TEST] disableScreenshots completed");
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
RCT_EXPORT_METHOD(enableScreenshots) {
|
|
106
|
+
NSLog(@"[TEST] enableScreenshots called");
|
|
107
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
108
|
+
isProtectionEnabled = NO;
|
|
109
|
+
NSLog(@"[TEST] Protection disabled, calling disableTrueScreenshotPrevention");
|
|
110
|
+
[self disableTrueScreenshotPrevention];
|
|
111
|
+
NSLog(@"[TEST] enableScreenshots completed");
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
RCT_EXPORT_METHOD(isScreenRecording:(RCTPromiseResolveBlock)resolve
|
|
116
|
+
rejecter:(RCTPromiseRejectBlock)reject) {
|
|
117
|
+
BOOL isRecording = [UIScreen mainScreen].isCaptured;
|
|
118
|
+
NSLog(@"[TEST] isScreenRecording called, result: %@", isRecording ? @"YES" : @"NO");
|
|
119
|
+
resolve(@(isRecording));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Clear and explicit method names
|
|
123
|
+
RCT_EXPORT_METHOD(subscribeToScreenshotAndScreenRecording) {
|
|
124
|
+
NSLog(@"[TEST] subscribeToScreenshotAndScreenRecording called - NEW EXPLICIT METHOD!");
|
|
125
|
+
[self startObserving];
|
|
126
|
+
NSLog(@"[TEST] subscribeToScreenshotAndScreenRecording completed");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
RCT_EXPORT_METHOD(unsubscribeFromScreenshotAndScreenRecording) {
|
|
130
|
+
NSLog(@"[TEST] unsubscribeFromScreenshotAndScreenRecording called");
|
|
131
|
+
[self stopObserving];
|
|
132
|
+
NSLog(@"[TEST] unsubscribeFromScreenshotAndScreenRecording completed");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Screenshot Prevention using Secure Text Field
|
|
136
|
+
- (void)enableTrueScreenshotPrevention {
|
|
137
|
+
NSLog(@"[TEST] enableTrueScreenshotPrevention called");
|
|
138
|
+
if (self.secureTextField == nil) {
|
|
139
|
+
NSLog(@"[TEST] Creating new secureTextField");
|
|
140
|
+
self.secureTextField = [[UITextField alloc] init];
|
|
141
|
+
self.secureTextField.userInteractionEnabled = NO;
|
|
142
|
+
self.secureTextField.secureTextEntry = YES;
|
|
143
|
+
|
|
144
|
+
UIWindow *keyWindow = [self getKeyWindow];
|
|
145
|
+
if (keyWindow != nil) {
|
|
146
|
+
NSLog(@"[TEST] Key window found, setting up secure text field");
|
|
147
|
+
[keyWindow makeKeyAndVisible];
|
|
148
|
+
|
|
149
|
+
// Make the app window a sublayer of the secure text field
|
|
150
|
+
[keyWindow.layer.superlayer addSublayer:self.secureTextField.layer];
|
|
151
|
+
|
|
152
|
+
// Add the window layer as a sublayer of the secure text field's first sublayer
|
|
153
|
+
NSArray *sublayers = self.secureTextField.layer.sublayers;
|
|
154
|
+
if (sublayers.count > 0) {
|
|
155
|
+
[sublayers.firstObject addSublayer:keyWindow.layer];
|
|
156
|
+
NSLog(@"[TEST] ✅ Secure text field configured successfully - Screenshot prevention ACTIVE!");
|
|
157
|
+
} else {
|
|
158
|
+
NSLog(@"[TEST] ❌ No sublayers found in secure text field");
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
NSLog(@"[TEST] ❌ Key window not found!");
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
NSLog(@"[TEST] Secure text field already exists, enabling secureTextEntry");
|
|
165
|
+
self.secureTextField.secureTextEntry = YES;
|
|
166
|
+
NSLog(@"[TEST] ✅ Secure text field re-enabled - Screenshot prevention ACTIVE!");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
- (void)disableTrueScreenshotPrevention {
|
|
171
|
+
NSLog(@"[TEST] disableTrueScreenshotPrevention called");
|
|
172
|
+
if (self.secureTextField != nil) {
|
|
173
|
+
NSLog(@"[TEST] Disabling secureTextEntry");
|
|
174
|
+
self.secureTextField.secureTextEntry = NO;
|
|
175
|
+
NSLog(@"[TEST] ✅ Screenshot prevention DISABLED");
|
|
176
|
+
} else {
|
|
177
|
+
NSLog(@"[TEST] Secure text field is nil");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
- (UIWindow *)getKeyWindow {
|
|
182
|
+
NSLog(@"[TEST] getKeyWindow called");
|
|
183
|
+
UIWindow *keyWindow = nil;
|
|
184
|
+
|
|
185
|
+
NSSet<UIScene *> *connectedScenes = [UIApplication sharedApplication].connectedScenes;
|
|
186
|
+
NSLog(@"[TEST] Connected scenes count: %lu", (unsigned long)connectedScenes.count);
|
|
187
|
+
|
|
188
|
+
for (UIScene *scene in connectedScenes) {
|
|
189
|
+
if ([scene isKindOfClass:[UIWindowScene class]]) {
|
|
190
|
+
UIWindowScene *windowScene = (UIWindowScene *)scene;
|
|
191
|
+
NSLog(@"[TEST] Found window scene with %lu windows", (unsigned long)windowScene.windows.count);
|
|
192
|
+
for (UIWindow *window in windowScene.windows) {
|
|
193
|
+
if (window.isKeyWindow) {
|
|
194
|
+
keyWindow = window;
|
|
195
|
+
NSLog(@"[TEST] ✅ Found key window using UIScene method");
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (keyWindow) break;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (keyWindow == nil) {
|
|
204
|
+
NSLog(@"[TEST] ❌ Key window not found using UIScene method!");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return keyWindow;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
- (void)dealloc {
|
|
211
|
+
NSLog(@"[TEST] dealloc called");
|
|
212
|
+
[self stopObserving];
|
|
213
|
+
[self disableTrueScreenshotPrevention];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
@end
|