@onekeyfe/react-native-capture-protection 0.0.0-stage → 3.0.165

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.
Files changed (54) hide show
  1. package/ACKNOWLEDGEMENTS.md +18 -0
  2. package/LICENSE +22 -0
  3. package/LICENSE.upstream +20 -0
  4. package/README.md +25 -2
  5. package/ReactNativeCaptureProtection.podspec +37 -0
  6. package/android/CMakeLists.txt +24 -0
  7. package/android/build.gradle +130 -0
  8. package/android/gradle.properties +4 -0
  9. package/android/src/main/AndroidManifest.xml +3 -0
  10. package/android/src/main/cpp/cpp-adapter.cpp +6 -0
  11. package/android/src/main/java/com/margelo/nitro/reactnativecaptureprotection/ReactNativeCaptureProtection.kt +285 -0
  12. package/android/src/main/java/com/margelo/nitro/reactnativecaptureprotection/ReactNativeCaptureProtectionPackage.kt +22 -0
  13. package/docs/SPEC.md +117 -0
  14. package/ios/ReactNativeCaptureProtection.swift +251 -0
  15. package/ios/tests/CaptureProtectionTests.swift +50 -0
  16. package/lib/module/ReactNativeCaptureProtection.nitro.js +4 -0
  17. package/lib/module/ReactNativeCaptureProtection.nitro.js.map +1 -0
  18. package/lib/module/index.js +61 -0
  19. package/lib/module/index.js.map +1 -0
  20. package/lib/module/package.json +1 -0
  21. package/lib/typescript/package.json +1 -0
  22. package/lib/typescript/src/ReactNativeCaptureProtection.nitro.d.ts +10 -0
  23. package/lib/typescript/src/ReactNativeCaptureProtection.nitro.d.ts.map +1 -0
  24. package/lib/typescript/src/index.d.ts +22 -0
  25. package/lib/typescript/src/index.d.ts.map +1 -0
  26. package/nitro.json +17 -0
  27. package/nitrogen/generated/android/c++/JFunc_void_double.hpp +75 -0
  28. package/nitrogen/generated/android/c++/JHybridReactNativeCaptureProtectionSpec.cpp +88 -0
  29. package/nitrogen/generated/android/c++/JHybridReactNativeCaptureProtectionSpec.hpp +65 -0
  30. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecaptureprotection/Func_void_double.kt +78 -0
  31. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecaptureprotection/HybridReactNativeCaptureProtectionSpec.kt +70 -0
  32. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecaptureprotection/reactnativecaptureprotectionOnLoad.kt +35 -0
  33. package/nitrogen/generated/android/reactnativecaptureprotection+autolinking.cmake +81 -0
  34. package/nitrogen/generated/android/reactnativecaptureprotection+autolinking.gradle +27 -0
  35. package/nitrogen/generated/android/reactnativecaptureprotectionOnLoad.cpp +56 -0
  36. package/nitrogen/generated/android/reactnativecaptureprotectionOnLoad.hpp +34 -0
  37. package/nitrogen/generated/ios/ReactNativeCaptureProtection+autolinking.rb +62 -0
  38. package/nitrogen/generated/ios/ReactNativeCaptureProtection-Swift-Cxx-Bridge.cpp +57 -0
  39. package/nitrogen/generated/ios/ReactNativeCaptureProtection-Swift-Cxx-Bridge.hpp +157 -0
  40. package/nitrogen/generated/ios/ReactNativeCaptureProtection-Swift-Cxx-Umbrella.hpp +46 -0
  41. package/nitrogen/generated/ios/ReactNativeCaptureProtectionAutolinking.mm +33 -0
  42. package/nitrogen/generated/ios/ReactNativeCaptureProtectionAutolinking.swift +26 -0
  43. package/nitrogen/generated/ios/c++/HybridReactNativeCaptureProtectionSpecSwift.cpp +11 -0
  44. package/nitrogen/generated/ios/c++/HybridReactNativeCaptureProtectionSpecSwift.hpp +98 -0
  45. package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  46. package/nitrogen/generated/ios/swift/Func_void_double.swift +46 -0
  47. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  48. package/nitrogen/generated/ios/swift/HybridReactNativeCaptureProtectionSpec.swift +57 -0
  49. package/nitrogen/generated/ios/swift/HybridReactNativeCaptureProtectionSpec_cxx.swift +187 -0
  50. package/nitrogen/generated/shared/c++/HybridReactNativeCaptureProtectionSpec.cpp +23 -0
  51. package/nitrogen/generated/shared/c++/HybridReactNativeCaptureProtectionSpec.hpp +66 -0
  52. package/package.json +173 -4
  53. package/src/ReactNativeCaptureProtection.nitro.ts +8 -0
  54. package/src/index.tsx +65 -0
package/docs/SPEC.md ADDED
@@ -0,0 +1,117 @@
1
+ # Capture protection contract
2
+
3
+ Status: Implemented. Native runtime acceptance is pending.
4
+
5
+ ## Purpose and scope
6
+
7
+ Protect wallet-sensitive screens without photo-library or file access. Support
8
+ OneKey iOS/Android callers of `prevent()`, `allow()` and `addListener()`. This is
9
+ not the full upstream API: providers, hooks, custom text/image overlays,
10
+ permission requests and media-library screenshot detection are not exported.
11
+ Web/desktop callers use their existing platform hooks and do not load this module.
12
+
13
+ ## Ownership and boundaries
14
+
15
+ The OS window and protection state belong to a process-wide native coordinator.
16
+ Each Nitro object has its own owner identifier and callback; main and background
17
+ JS runtimes have separate objects/heaps and initialize independently. One owner's
18
+ `allow()` must not release another owner's protection. JS callbacks remain scoped
19
+ to their owning object/runtime. No image bytes, album entries, file paths or
20
+ product secrets enter this module. Only the UI runtime uses it in the app.
21
+
22
+ ## Public API and defaults
23
+
24
+ - `CaptureProtection.prevent(): Promise<void>` enables screenshot, recording and
25
+ app-switcher protection together. Repeated calls by one object are idempotent.
26
+ - `CaptureProtection.allow(): Promise<void>` releases only that object's protection.
27
+ - `CaptureProtection.addListener(callback): { remove(): void }` registers a local
28
+ subscriber; removal is idempotent. At most 128 JS subscribers per runtime.
29
+ - `CaptureEventType`: NONE=0, RECORDING=1, END_RECORDING=2, CAPTURED=3,
30
+ APP_SWITCHING=4, UNKNOWN=5, ALLOW=8, PREVENT_SCREEN_CAPTURE=16,
31
+ PREVENT_SCREEN_RECORDING=32, PREVENT_SCREEN_APP_SWITCHING=64.
32
+ - Nitro methods are `prevent`, `allow`, `setListener(callback | undefined)`.
33
+ Protection failures reject; missing native registration throws instead of
34
+ silently pretending sensitive screens are protected.
35
+
36
+ ## Lifecycle and concurrency
37
+
38
+ All native state and window operations run on the UI thread in dispatch order.
39
+ There is at most one native subscription per object; JS fans events out to its
40
+ current subscribers. Removing the final subscriber releases its native callback.
41
+ Native observers start on first owner/subscriber and stop on the last. Callbacks
42
+ are weakly connected to objects; disposal releases that owner's state and listener.
43
+ Android resolves the foreground activity through the application UI ReactHost
44
+ and lifecycle tracking, never Nitro's last-initialized global React context.
45
+ Android reapplies FLAG_SECURE on activity resume while protection is requested,
46
+ unregisters screenshot callbacks on pause and unregisters activity/display
47
+ observers when no owners/subscribers remain. iOS reload notification resets
48
+ obsolete owners and observers. Protection is fail-closed across activity pause;
49
+ activity destruction does not clear the owners of still-running JS runtimes.
50
+ Events reflect OS notifications, not screenshot image contents. No deduplication
51
+ or 1-second artificial event reset is added; the existing app hook debounces warnings.
52
+
53
+ ## Data, cache and identity
54
+
55
+ Only in-memory owner IDs, listeners, observer registrations and overlay windows
56
+ are retained. No storage, persistence, cache, migrations or network access.
57
+
58
+ ## Platform contract
59
+
60
+ | Behavior | iOS | Android |
61
+ | --------------------- | --------------------------------------------------- | ----------------------------------------------------- |
62
+ | Screenshot protection | Upstream-derived secure UITextField layer technique | FLAG_SECURE |
63
+ | Recording protection | Secure layer plus opaque window while captured | FLAG_SECURE |
64
+ | App switcher | Opaque window before resign/background | FLAG_SECURE |
65
+ | Screenshot event | UIApplication notification | Android 14+ system callback |
66
+ | Recording event | UIScreen capture change notification | Upstream-derived virtual-display heuristic |
67
+ | Permissions | None | DETECT_SCREEN_CAPTURE, normal permission, Android 14+ |
68
+
69
+ Android below 14 intentionally has no screenshot notification; blocking remains
70
+ available. Android virtual-display events are a heuristic and may also represent
71
+ casting/external displays; they are not proof that a recording was made.
72
+ iOS secure-text-field wrapping is inherited from upstream, is not a documented
73
+ system-wide screenshot prevention API, and requires OS-version/device acceptance.
74
+ Existing recordings when subscribing emit RECORDING when identifiable. Existing
75
+ Android private virtual displays may not be visible to DisplayManager enumeration.
76
+
77
+ ## Failure, fallback and safety
78
+
79
+ Missing foreground activity/window or unavailable secure layer rejects prevent.
80
+ Never fall back to MediaStore queries, filesystem scanning or storage permissions.
81
+ Screenshot observer errors must not disable FLAG_SECURE. Only FLAG_SECURE flags
82
+ introduced by this coordinator may be cleared; preserve pre-existing flags.
83
+ A native coordinator admits at most 32 owner/subscriber identities, rejecting
84
+ excess registration. No custom overlays, bytes or unbounded event queues.
85
+
86
+ ## Performance and resources
87
+
88
+ UI work is limited to flags, notifications, layer configuration and at most two
89
+ opaque windows on iOS. No polling, I/O, image processing or detached coroutines.
90
+ Recording/display changes are event-driven. Listener registration synchronously marshals to the UI thread; Android rejects
91
+ a wait exceeding 5 seconds. Protection operations return promises. No performance
92
+ claim is made.
93
+
94
+ ## Conformance and acceptance
95
+
96
+ Source: `src/index.tsx`, `ios/ReactNativeCaptureProtection.swift` and Android
97
+ `ReactNativeCaptureProtection.kt`. JS tests cover listener fan-out/removal and
98
+ missing registration. iOS XCTest is provided for independent object subscriptions,
99
+ disposal and secure-layer owner isolation; physical capture acceptance remains
100
+ separate.
101
+ Required runtime acceptance: prevent/allow, nested sensitive pages, repeated
102
+ navigation, recording started before/after subscription, background snapshots,
103
+ activity recreation, subscription cleanup, and main/background object isolation.
104
+ Inspect merged Android manifest and packaged iOS plist for media-read permissions.
105
+ Build/type/unit results do not prove screenshot prevention on physical devices.
106
+
107
+ ## Template deviations and attribution
108
+
109
+ Generated from `yarn create:module react-native-capture-protection`. Remove the
110
+ hello example and unused Babel module-resolver alias only; retain template build
111
+ conventions. The alias is not used by this package and the template does not
112
+ declare its Babel plugin. Match the repository React Native patched dependency. Use the repository
113
+ FlatCompat ESLint pattern because the template calls an unavailable
114
+ createEslintConfig export. The XCTest spec requires an app host for UIKit tests.
115
+ Add SPEC, focused tests,
116
+ ACKNOWLEDGEMENTS and the upstream MIT notice. See ACKNOWLEDGEMENTS.md for source
117
+ provenance and the precise subset adapted.
@@ -0,0 +1,251 @@
1
+ // Adapted from react-native-capture-protection 2.3.0 by lethe; see LICENSE.upstream.
2
+ import Foundation
3
+ import NitroModules
4
+ import React
5
+ import UIKit
6
+
7
+ class ReactNativeCaptureProtection: HybridReactNativeCaptureProtectionSpec {
8
+ private let owner = UUID()
9
+ private var listener: ((Double) -> Void)?
10
+
11
+ func prevent() throws -> Promise<Void> {
12
+ let promise = Promise<Void>()
13
+ DispatchQueue.main.async {
14
+ do {
15
+ try CaptureProtectionCoordinator.shared.prevent(owner: self.owner)
16
+ promise.resolve(withResult: ())
17
+ } catch {
18
+ promise.reject(withError: error)
19
+ }
20
+ }
21
+ return promise
22
+ }
23
+
24
+ func allow() throws -> Promise<Void> {
25
+ let promise = Promise<Void>()
26
+ DispatchQueue.main.async {
27
+ CaptureProtectionCoordinator.shared.allow(owner: self.owner)
28
+ promise.resolve(withResult: ())
29
+ }
30
+ return promise
31
+ }
32
+
33
+ func setListener(callback: ((Double) -> Void)?) throws {
34
+ try onMain {
35
+ let coordinator = CaptureProtectionCoordinator.shared
36
+ if let callback {
37
+ try coordinator.subscribe(owner: self.owner) { [weak self] event in
38
+ self?.listener?(event)
39
+ }
40
+ self.listener = callback
41
+ coordinator.emitRecordingState(owner: self.owner)
42
+ } else {
43
+ self.listener = nil
44
+ coordinator.unsubscribe(owner: self.owner)
45
+ }
46
+ }
47
+ }
48
+
49
+ func dispose() {
50
+ DispatchQueue.main.async {
51
+ self.listener = nil
52
+ CaptureProtectionCoordinator.shared.unsubscribe(owner: self.owner)
53
+ CaptureProtectionCoordinator.shared.allow(owner: self.owner)
54
+ }
55
+ }
56
+
57
+ deinit {
58
+ let identifier = owner
59
+ DispatchQueue.main.async {
60
+ CaptureProtectionCoordinator.shared.unsubscribe(owner: identifier)
61
+ CaptureProtectionCoordinator.shared.allow(owner: identifier)
62
+ }
63
+ }
64
+
65
+ private func onMain(_ operation: () throws -> Void) rethrows {
66
+ if Thread.isMainThread {
67
+ try operation()
68
+ } else {
69
+ try DispatchQueue.main.sync(execute: operation)
70
+ }
71
+ }
72
+ }
73
+
74
+ private final class CaptureProtectionCoordinator {
75
+ static let shared = CaptureProtectionCoordinator()
76
+ private var owners = Set<UUID>()
77
+ private var listeners: [UUID: (Double) -> Void] = [:]
78
+ private var observers: [NSObjectProtocol] = []
79
+ private var secureField: UITextField?
80
+ private weak var protectedWindow: UIWindow?
81
+ private var recordWindow: UIWindow?
82
+ private var switcherWindow: UIWindow?
83
+ private var recording = false
84
+
85
+ private var window: UIWindow? {
86
+ if let delegated = UIApplication.shared.delegate?.window ?? nil { return delegated }
87
+ return UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
88
+ .flatMap { $0.windows }.first { $0.isKeyWindow }
89
+ }
90
+
91
+ private func checkCapacity(_ owner: UUID) throws {
92
+ let identities = owners.union(listeners.keys)
93
+ if !identities.contains(owner) && identities.count >= 32 {
94
+ throw failure("Capture protection supports at most 32 native owners.")
95
+ }
96
+ }
97
+
98
+ func prevent(owner: UUID) throws {
99
+ try checkCapacity(owner)
100
+ guard let window else { throw failure("No application window is available.") }
101
+ if owners.isEmpty {
102
+ try secureScreenshot(window: window)
103
+ }
104
+ owners.insert(owner)
105
+ startObservers()
106
+ refreshRecording()
107
+ if UIApplication.shared.applicationState != .active { showSwitcher() }
108
+ }
109
+
110
+ func allow(owner: UUID) {
111
+ owners.remove(owner)
112
+ if owners.isEmpty {
113
+ secureField?.isSecureTextEntry = false
114
+ removeRecordWindow()
115
+ removeSwitcherWindow()
116
+ }
117
+ stopObserversIfIdle()
118
+ }
119
+
120
+ func subscribe(owner: UUID, callback: @escaping (Double) -> Void) throws {
121
+ try checkCapacity(owner)
122
+ listeners[owner] = callback
123
+ startObservers()
124
+ }
125
+
126
+ func unsubscribe(owner: UUID) {
127
+ listeners.removeValue(forKey: owner)
128
+ stopObserversIfIdle()
129
+ }
130
+
131
+ func emitRecordingState(owner: UUID) {
132
+ if UIScreen.main.isCaptured { listeners[owner]?(1) }
133
+ }
134
+
135
+ private func emit(_ event: Double) {
136
+ for (owner, callback) in Array(listeners) where listeners[owner] != nil {
137
+ callback(event)
138
+ }
139
+ }
140
+
141
+ private func startObservers() {
142
+ guard observers.isEmpty else { return }
143
+ let center = NotificationCenter.default
144
+ recording = UIScreen.main.isCaptured
145
+ observers.append(center.addObserver(
146
+ forName: UIApplication.userDidTakeScreenshotNotification, object: nil, queue: .main
147
+ ) { [weak self] _ in self?.emit(3) })
148
+ observers.append(center.addObserver(
149
+ forName: UIScreen.capturedDidChangeNotification, object: nil, queue: .main
150
+ ) { [weak self] _ in self?.refreshRecording() })
151
+ for name in [UIApplication.willResignActiveNotification, UIApplication.didEnterBackgroundNotification] {
152
+ observers.append(center.addObserver(forName: name, object: nil, queue: .main) {
153
+ [weak self] _ in
154
+ self?.showSwitcher()
155
+ })
156
+ }
157
+ observers.append(center.addObserver(
158
+ forName: UIApplication.didBecomeActiveNotification, object: nil, queue: .main
159
+ ) { [weak self] _ in
160
+ self?.removeSwitcherWindow()
161
+ self?.refreshRecording()
162
+ })
163
+ observers.append(center.addObserver(
164
+ forName: NSNotification.Name.RCTTriggerReloadCommand, object: nil, queue: .main
165
+ ) { [weak self] _ in self?.reset() })
166
+ }
167
+
168
+ private func stopObserversIfIdle() {
169
+ guard owners.isEmpty && listeners.isEmpty else { return }
170
+ for observer in observers { NotificationCenter.default.removeObserver(observer) }
171
+ observers.removeAll()
172
+ }
173
+
174
+ private func reset() {
175
+ owners.removeAll()
176
+ listeners.removeAll()
177
+ secureField?.isSecureTextEntry = false
178
+ removeRecordWindow()
179
+ removeSwitcherWindow()
180
+ stopObserversIfIdle()
181
+ }
182
+
183
+ private func refreshRecording() {
184
+ let captured = UIScreen.main.isCaptured
185
+ if captured != recording {
186
+ recording = captured
187
+ emit(captured ? 1 : 2)
188
+ }
189
+ if captured && !owners.isEmpty {
190
+ if recordWindow == nil { recordWindow = makeOverlay() }
191
+ } else {
192
+ removeRecordWindow()
193
+ }
194
+ }
195
+
196
+ private func secureScreenshot(window: UIWindow) throws {
197
+ if let secureField, protectedWindow === window {
198
+ secureField.isSecureTextEntry = true
199
+ return
200
+ }
201
+ let field = UITextField(frame: .zero)
202
+ field.isUserInteractionEnabled = false
203
+ field.isSecureTextEntry = true
204
+ field.layoutIfNeeded()
205
+ guard let canvas = field.layer.sublayers?.last else {
206
+ throw failure("The secure text layer is unavailable.")
207
+ }
208
+ // Keep the upstream secure-layer technique; no photo or file access is involved.
209
+ window.layer.superlayer?.addSublayer(field.layer)
210
+ canvas.addSublayer(window.layer)
211
+ secureField?.isSecureTextEntry = false
212
+ secureField = field
213
+ protectedWindow = window
214
+ }
215
+
216
+ private func makeOverlay() -> UIWindow? {
217
+ guard let protectedWindow = protectedWindow ?? window else { return nil }
218
+ let overlay: UIWindow
219
+ if let scene = protectedWindow.windowScene {
220
+ overlay = UIWindow(windowScene: scene)
221
+ overlay.frame = protectedWindow.frame
222
+ } else {
223
+ overlay = UIWindow(frame: protectedWindow.frame)
224
+ }
225
+ let controller = UIViewController()
226
+ controller.view.backgroundColor = .white
227
+ overlay.rootViewController = controller
228
+ overlay.windowLevel = .alert + 1
229
+ overlay.isHidden = false
230
+ return overlay
231
+ }
232
+
233
+ private func showSwitcher() {
234
+ guard !owners.isEmpty else { return }
235
+ if switcherWindow == nil { switcherWindow = makeOverlay() }
236
+ }
237
+
238
+ private func removeRecordWindow() {
239
+ recordWindow?.isHidden = true
240
+ recordWindow = nil
241
+ }
242
+
243
+ private func removeSwitcherWindow() {
244
+ switcherWindow?.isHidden = true
245
+ switcherWindow = nil
246
+ }
247
+
248
+ private func failure(_ message: String) -> NSError {
249
+ NSError(domain: "CaptureProtection", code: 1, userInfo: [NSLocalizedDescriptionKey: message])
250
+ }
251
+ }
@@ -0,0 +1,50 @@
1
+ import NitroModules
2
+ import UIKit
3
+ import XCTest
4
+ @testable import ReactNativeCaptureProtection
5
+
6
+ @MainActor
7
+ final class CaptureProtectionTests: XCTestCase {
8
+ func testNativeSubscriptionsRemainScopedToTheirObjects() throws {
9
+ let first = ReactNativeCaptureProtection()
10
+ let second = ReactNativeCaptureProtection()
11
+ var firstEvents: [Double] = []
12
+ var secondEvents: [Double] = []
13
+ try first.setListener { firstEvents.append($0) }
14
+ try second.setListener { secondEvents.append($0) }
15
+ NotificationCenter.default.post(name: UIApplication.userDidTakeScreenshotNotification, object: nil)
16
+ XCTAssertEqual(firstEvents, [3])
17
+ XCTAssertEqual(secondEvents, [3])
18
+ try first.setListener(callback: nil)
19
+ NotificationCenter.default.post(name: UIApplication.userDidTakeScreenshotNotification, object: nil)
20
+ XCTAssertEqual(firstEvents, [3])
21
+ XCTAssertEqual(secondEvents, [3, 3])
22
+ try second.setListener(callback: nil)
23
+ }
24
+
25
+ func testDisposalStopsNativeEvents() async throws {
26
+ let instance = ReactNativeCaptureProtection()
27
+ var events: [Double] = []
28
+ try instance.setListener { events.append($0) }
29
+ instance.dispose()
30
+ // allow is queued after disposal and provides a deterministic UI-thread barrier.
31
+ try await instance.allow().await()
32
+ NotificationCenter.default.post(name: UIApplication.userDidTakeScreenshotNotification, object: nil)
33
+ XCTAssertTrue(events.isEmpty)
34
+ }
35
+
36
+ func testOneOwnerCannotReleaseAnotherOwnersSecureLayer() async throws {
37
+ let window = try XCTUnwrap(UIApplication.shared.delegate?.window ?? nil)
38
+ let first = ReactNativeCaptureProtection()
39
+ let second = ReactNativeCaptureProtection()
40
+ try await first.prevent().await()
41
+ try await second.prevent().await()
42
+ let field = window.layer.superlayer?.superlayer?.delegate as? UITextField
43
+ XCTAssertNotNil(field)
44
+ XCTAssertEqual(field?.isSecureTextEntry, true)
45
+ try await first.allow().await()
46
+ XCTAssertEqual(field?.isSecureTextEntry, true)
47
+ try await second.allow().await()
48
+ XCTAssertEqual(field?.isSecureTextEntry, false)
49
+ }
50
+ }
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+
3
+ export {};
4
+ //# sourceMappingURL=ReactNativeCaptureProtection.nitro.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sourceRoot":"../../src","sources":["ReactNativeCaptureProtection.nitro.ts"],"mappings":"","ignoreList":[]}
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+
3
+ import { NitroModules } from 'react-native-nitro-modules';
4
+ // Adapted from react-native-capture-protection 2.3.0; see LICENSE.upstream.
5
+ export let CaptureEventType = /*#__PURE__*/function (CaptureEventType) {
6
+ CaptureEventType[CaptureEventType["NONE"] = 0] = "NONE";
7
+ CaptureEventType[CaptureEventType["RECORDING"] = 1] = "RECORDING";
8
+ CaptureEventType[CaptureEventType["END_RECORDING"] = 2] = "END_RECORDING";
9
+ CaptureEventType[CaptureEventType["CAPTURED"] = 3] = "CAPTURED";
10
+ CaptureEventType[CaptureEventType["APP_SWITCHING"] = 4] = "APP_SWITCHING";
11
+ CaptureEventType[CaptureEventType["UNKNOWN"] = 5] = "UNKNOWN";
12
+ CaptureEventType[CaptureEventType["ALLOW"] = 8] = "ALLOW";
13
+ CaptureEventType[CaptureEventType["PREVENT_SCREEN_CAPTURE"] = 16] = "PREVENT_SCREEN_CAPTURE";
14
+ CaptureEventType[CaptureEventType["PREVENT_SCREEN_RECORDING"] = 32] = "PREVENT_SCREEN_RECORDING";
15
+ CaptureEventType[CaptureEventType["PREVENT_SCREEN_APP_SWITCHING"] = 64] = "PREVENT_SCREEN_APP_SWITCHING";
16
+ return CaptureEventType;
17
+ }({});
18
+ let native;
19
+ const subscribers = new Map();
20
+ function getNative() {
21
+ native ??= NitroModules.createHybridObject('ReactNativeCaptureProtection');
22
+ return native;
23
+ }
24
+ export const CaptureProtection = {
25
+ prevent() {
26
+ return getNative().prevent();
27
+ },
28
+ allow() {
29
+ return getNative().allow();
30
+ },
31
+ addListener(callback) {
32
+ if (subscribers.size >= 128) {
33
+ throw new Error('Capture protection supports at most 128 subscribers.');
34
+ }
35
+ const id = Symbol();
36
+ subscribers.set(id, callback);
37
+ if (subscribers.size === 1) {
38
+ try {
39
+ getNative().setListener(event => {
40
+ // Read current subscriptions so queued events cannot reach removed callers.
41
+ for (const [key, listener] of Array.from(subscribers)) {
42
+ if (subscribers.has(key)) {
43
+ listener(event);
44
+ }
45
+ }
46
+ });
47
+ } catch (error) {
48
+ subscribers.delete(id);
49
+ throw error;
50
+ }
51
+ }
52
+ return {
53
+ remove() {
54
+ if (subscribers.delete(id) && subscribers.size === 0) {
55
+ getNative().setListener(undefined);
56
+ }
57
+ }
58
+ };
59
+ }
60
+ };
61
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["NitroModules","CaptureEventType","native","subscribers","Map","getNative","createHybridObject","CaptureProtection","prevent","allow","addListener","callback","size","Error","id","Symbol","set","setListener","event","key","listener","Array","from","has","error","delete","remove","undefined"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SAASA,YAAY,QAAQ,4BAA4B;AAGzD;AACA,WAAYC,gBAAgB,0BAAhBA,gBAAgB;EAAhBA,gBAAgB,CAAhBA,gBAAgB;EAAhBA,gBAAgB,CAAhBA,gBAAgB;EAAhBA,gBAAgB,CAAhBA,gBAAgB;EAAhBA,gBAAgB,CAAhBA,gBAAgB;EAAhBA,gBAAgB,CAAhBA,gBAAgB;EAAhBA,gBAAgB,CAAhBA,gBAAgB;EAAhBA,gBAAgB,CAAhBA,gBAAgB;EAAhBA,gBAAgB,CAAhBA,gBAAgB;EAAhBA,gBAAgB,CAAhBA,gBAAgB;EAAhBA,gBAAgB,CAAhBA,gBAAgB;EAAA,OAAhBA,gBAAgB;AAAA;AAc5B,IAAIC,MAAgD;AACpD,MAAMC,WAAW,GAAG,IAAIC,GAAG,CAA0B,CAAC;AAEtD,SAASC,SAASA,CAAA,EAAiC;EACjDH,MAAM,KAAKF,YAAY,CAACM,kBAAkB,CACxC,8BACF,CAAC;EACD,OAAOJ,MAAM;AACf;AAEA,OAAO,MAAMK,iBAAiB,GAAG;EAC/BC,OAAOA,CAAA,EAAkB;IACvB,OAAOH,SAAS,CAAC,CAAC,CAACG,OAAO,CAAC,CAAC;EAC9B,CAAC;EACDC,KAAKA,CAAA,EAAkB;IACrB,OAAOJ,SAAS,CAAC,CAAC,CAACI,KAAK,CAAC,CAAC;EAC5B,CAAC;EACDC,WAAWA,CAACC,QAAyB,EAAsB;IACzD,IAAIR,WAAW,CAACS,IAAI,IAAI,GAAG,EAAE;MAC3B,MAAM,IAAIC,KAAK,CAAC,sDAAsD,CAAC;IACzE;IACA,MAAMC,EAAE,GAAGC,MAAM,CAAC,CAAC;IACnBZ,WAAW,CAACa,GAAG,CAACF,EAAE,EAAEH,QAAQ,CAAC;IAC7B,IAAIR,WAAW,CAACS,IAAI,KAAK,CAAC,EAAE;MAC1B,IAAI;QACFP,SAAS,CAAC,CAAC,CAACY,WAAW,CAAEC,KAAK,IAAK;UACjC;UACA,KAAK,MAAM,CAACC,GAAG,EAAEC,QAAQ,CAAC,IAAIC,KAAK,CAACC,IAAI,CAACnB,WAAW,CAAC,EAAE;YACrD,IAAIA,WAAW,CAACoB,GAAG,CAACJ,GAAG,CAAC,EAAE;cACxBC,QAAQ,CAACF,KAAyB,CAAC;YACrC;UACF;QACF,CAAC,CAAC;MACJ,CAAC,CAAC,OAAOM,KAAK,EAAE;QACdrB,WAAW,CAACsB,MAAM,CAACX,EAAE,CAAC;QACtB,MAAMU,KAAK;MACb;IACF;IACA,OAAO;MACLE,MAAMA,CAAA,EAAG;QACP,IAAIvB,WAAW,CAACsB,MAAM,CAACX,EAAE,CAAC,IAAIX,WAAW,CAACS,IAAI,KAAK,CAAC,EAAE;UACpDP,SAAS,CAAC,CAAC,CAACY,WAAW,CAACU,SAAS,CAAC;QACpC;MACF;IACF,CAAC;EACH;AACF,CAAC","ignoreList":[]}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,10 @@
1
+ import type { HybridObject } from 'react-native-nitro-modules';
2
+ export interface ReactNativeCaptureProtection extends HybridObject<{
3
+ ios: 'swift';
4
+ android: 'kotlin';
5
+ }> {
6
+ prevent(): Promise<void>;
7
+ allow(): Promise<void>;
8
+ setListener(callback: ((event: number) => void) | undefined): void;
9
+ }
10
+ //# sourceMappingURL=ReactNativeCaptureProtection.nitro.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReactNativeCaptureProtection.nitro.d.ts","sourceRoot":"","sources":["../../../src/ReactNativeCaptureProtection.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,MAAM,WAAW,4BACf,SAAQ,YAAY,CAAC;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC;IACzD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,WAAW,CAAC,QAAQ,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC;CACpE"}
@@ -0,0 +1,22 @@
1
+ export declare enum CaptureEventType {
2
+ NONE = 0,
3
+ RECORDING = 1,
4
+ END_RECORDING = 2,
5
+ CAPTURED = 3,
6
+ APP_SWITCHING = 4,
7
+ UNKNOWN = 5,
8
+ ALLOW = 8,
9
+ PREVENT_SCREEN_CAPTURE = 16,
10
+ PREVENT_SCREEN_RECORDING = 32,
11
+ PREVENT_SCREEN_APP_SWITCHING = 64
12
+ }
13
+ type CaptureCallback = (event: CaptureEventType) => void;
14
+ export declare const CaptureProtection: {
15
+ prevent(): Promise<void>;
16
+ allow(): Promise<void>;
17
+ addListener(callback: CaptureCallback): {
18
+ remove(): void;
19
+ };
20
+ };
21
+ export {};
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAIA,oBAAY,gBAAgB;IAC1B,IAAI,IAAI;IACR,SAAS,IAAI;IACb,aAAa,IAAI;IACjB,QAAQ,IAAI;IACZ,aAAa,IAAI;IACjB,OAAO,IAAI;IACX,KAAK,IAAI;IACT,sBAAsB,KAAK;IAC3B,wBAAwB,KAAK;IAC7B,4BAA4B,KAAK;CAClC;AAED,KAAK,eAAe,GAAG,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;AAWzD,eAAO,MAAM,iBAAiB;eACjB,OAAO,CAAC,IAAI,CAAC;aAGf,OAAO,CAAC,IAAI,CAAC;0BAGA,eAAe,GAAG;QAAE,MAAM,IAAI,IAAI,CAAA;KAAE;CA6B3D,CAAC"}
package/nitro.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "cxxNamespace": ["reactnativecaptureprotection"],
3
+ "ios": {
4
+ "iosModuleName": "ReactNativeCaptureProtection"
5
+ },
6
+ "android": {
7
+ "androidNamespace": ["reactnativecaptureprotection"],
8
+ "androidCxxLibName": "reactnativecaptureprotection"
9
+ },
10
+ "autolinking": {
11
+ "ReactNativeCaptureProtection": {
12
+ "swift": "ReactNativeCaptureProtection",
13
+ "kotlin": "ReactNativeCaptureProtection"
14
+ }
15
+ },
16
+ "ignorePaths": ["node_modules"]
17
+ }
@@ -0,0 +1,75 @@
1
+ ///
2
+ /// JFunc_void_double.hpp
3
+ /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ /// https://github.com/mrousavy/nitro
5
+ /// Copyright © Marc Rousavy @ Margelo
6
+ ///
7
+
8
+ #pragma once
9
+
10
+ #include <fbjni/fbjni.h>
11
+ #include <functional>
12
+
13
+ #include <functional>
14
+ #include <NitroModules/JNICallable.hpp>
15
+
16
+ namespace margelo::nitro::reactnativecaptureprotection {
17
+
18
+ using namespace facebook;
19
+
20
+ /**
21
+ * Represents the Java/Kotlin callback `(event: Double) -> Unit`.
22
+ * This can be passed around between C++ and Java/Kotlin.
23
+ */
24
+ struct JFunc_void_double: public jni::JavaClass<JFunc_void_double> {
25
+ public:
26
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/reactnativecaptureprotection/Func_void_double;";
27
+
28
+ public:
29
+ /**
30
+ * Invokes the function this `JFunc_void_double` instance holds through JNI.
31
+ */
32
+ void invoke(double event) const {
33
+ static const auto method = javaClassStatic()->getMethod<void(double /* event */)>("invoke");
34
+ method(self(), event);
35
+ }
36
+ };
37
+
38
+ /**
39
+ * An implementation of Func_void_double that is backed by a C++ implementation (using `std::function<...>`)
40
+ */
41
+ class JFunc_void_double_cxx final: public jni::HybridClass<JFunc_void_double_cxx, JFunc_void_double> {
42
+ public:
43
+ static jni::local_ref<JFunc_void_double::javaobject> fromCpp(const std::function<void(double /* event */)>& func) {
44
+ return JFunc_void_double_cxx::newObjectCxxArgs(func);
45
+ }
46
+
47
+ public:
48
+ /**
49
+ * Invokes the C++ `std::function<...>` this `JFunc_void_double_cxx` instance holds.
50
+ */
51
+ void invoke_cxx(double event) {
52
+ _func(event);
53
+ }
54
+
55
+ public:
56
+ [[nodiscard]]
57
+ inline const std::function<void(double /* event */)>& getFunction() const {
58
+ return _func;
59
+ }
60
+
61
+ public:
62
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/reactnativecaptureprotection/Func_void_double_cxx;";
63
+ static void registerNatives() {
64
+ registerHybrid({makeNativeMethod("invoke_cxx", JFunc_void_double_cxx::invoke_cxx)});
65
+ }
66
+
67
+ private:
68
+ explicit JFunc_void_double_cxx(const std::function<void(double /* event */)>& func): _func(func) { }
69
+
70
+ private:
71
+ friend HybridBase;
72
+ std::function<void(double /* event */)> _func;
73
+ };
74
+
75
+ } // namespace margelo::nitro::reactnativecaptureprotection