cobrowse-sdk-react-native 2.11.2-unredaction.0 → 2.12.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 +1 -1
- package/android/src/main/java/io/cobrowse/reactnative/CobrowseIO.java +22 -0
- package/android/src/main/java/io/cobrowse/reactnative/CobrowseIOModule.java +84 -58
- package/android/src/main/java/io/cobrowse/reactnative/TreeUtils.java +5 -31
- package/cobrowse-sdk-react-native.podspec +1 -1
- package/ios/RCTCBIOTreeUtils.h +1 -7
- package/ios/RCTCBIOTreeUtils.m +4 -30
- package/ios/RCTCobrowseIO.h +14 -0
- package/ios/RCTCobrowseIO.m +68 -46
- package/package.json +7 -7
package/android/build.gradle
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
package io.cobrowse.reactnative;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.view.View;
|
|
5
|
+
|
|
6
|
+
import java.util.List;
|
|
7
|
+
|
|
8
|
+
import androidx.annotation.NonNull;
|
|
9
|
+
import androidx.annotation.Nullable;
|
|
10
|
+
|
|
11
|
+
public abstract class CobrowseIO {
|
|
12
|
+
public interface Delegate {
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public interface RedactionDelegate extends Delegate {
|
|
16
|
+
@Nullable
|
|
17
|
+
List<View> redactedViews(@NonNull final Activity activity);
|
|
18
|
+
|
|
19
|
+
@Nullable
|
|
20
|
+
List<View> unredactedViews(@NonNull final Activity activity);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -4,6 +4,8 @@ import android.app.Activity;
|
|
|
4
4
|
import android.os.Handler;
|
|
5
5
|
import android.util.Log;
|
|
6
6
|
import android.view.View;
|
|
7
|
+
import android.view.ViewGroup;
|
|
8
|
+
import android.view.ViewParent;
|
|
7
9
|
|
|
8
10
|
import com.facebook.react.bridge.Promise;
|
|
9
11
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
@@ -15,13 +17,10 @@ import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
|
15
17
|
import com.facebook.react.uimanager.NativeViewHierarchyManager;
|
|
16
18
|
import com.facebook.react.uimanager.UIBlock;
|
|
17
19
|
import com.facebook.react.uimanager.UIManagerModule;
|
|
18
|
-
import com.facebook.react.views.view.ReactViewGroup;
|
|
19
20
|
|
|
20
21
|
import java.util.ArrayList;
|
|
21
|
-
import java.util.HashMap;
|
|
22
22
|
import java.util.HashSet;
|
|
23
23
|
import java.util.List;
|
|
24
|
-
import java.util.Map;
|
|
25
24
|
import java.util.Set;
|
|
26
25
|
|
|
27
26
|
import androidx.annotation.NonNull;
|
|
@@ -35,7 +34,7 @@ public class CobrowseIOModule extends ReactContextBaseJavaModule
|
|
|
35
34
|
CobrowseIO.SessionLoadDelegate, CobrowseIO.RedactionDelegate,
|
|
36
35
|
CobrowseIO.RemoteControlRequestDelegate {
|
|
37
36
|
|
|
38
|
-
|
|
37
|
+
private static final String SESSION_LOADED = "session.loaded";
|
|
39
38
|
private static final String SESSION_UPDATED = "session.updated";
|
|
40
39
|
private static final String SESSION_ENDED = "session.ended";
|
|
41
40
|
private static final String SESSION_REQUESTED = "session.requested";
|
|
@@ -60,6 +59,8 @@ public class CobrowseIOModule extends ReactContextBaseJavaModule
|
|
|
60
59
|
});
|
|
61
60
|
}
|
|
62
61
|
|
|
62
|
+
public static io.cobrowse.reactnative.CobrowseIO.Delegate delegate;
|
|
63
|
+
|
|
63
64
|
@NonNull
|
|
64
65
|
public String getName() {
|
|
65
66
|
return "CobrowseIO";
|
|
@@ -93,6 +94,11 @@ public class CobrowseIOModule extends ReactContextBaseJavaModule
|
|
|
93
94
|
.emit(SESSION_REQUESTED, Conversion.convert(session));
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
@Override
|
|
98
|
+
public void handleRemoteControlRequest(@NonNull Activity activity, @NonNull Session session) {
|
|
99
|
+
// no-op, this will be handed on the JS side via an "updated" event handler
|
|
100
|
+
// this stub just disables the default native prompt in the SDK
|
|
101
|
+
}
|
|
96
102
|
@ReactMethod
|
|
97
103
|
public void setUnredactedTags(final ReadableArray reactTags, final Promise promise) {
|
|
98
104
|
synchronized (unredactedTags) {
|
|
@@ -103,7 +109,7 @@ public class CobrowseIOModule extends ReactContextBaseJavaModule
|
|
|
103
109
|
}
|
|
104
110
|
}
|
|
105
111
|
|
|
106
|
-
private Set<View>
|
|
112
|
+
private Set<View> getUnredactedViews(@NonNull final Activity activity) {
|
|
107
113
|
synchronized (unredactedTags) {
|
|
108
114
|
HashSet<View> unredacted = new HashSet<>();
|
|
109
115
|
for (Integer i : unredactedTags) {
|
|
@@ -113,68 +119,87 @@ public class CobrowseIOModule extends ReactContextBaseJavaModule
|
|
|
113
119
|
Log.i("CobrowseIO", "Failed to find unredacted view for tag " + i + ", error = " + e.getMessage());
|
|
114
120
|
}
|
|
115
121
|
}
|
|
122
|
+
if (delegate instanceof io.cobrowse.reactnative.CobrowseIO.RedactionDelegate) {
|
|
123
|
+
List<View> views = ((io.cobrowse.reactnative.CobrowseIO.RedactionDelegate) delegate).unredactedViews(activity);
|
|
124
|
+
if (views != null) unredacted.addAll(views);
|
|
125
|
+
}
|
|
116
126
|
return unredacted;
|
|
117
127
|
}
|
|
118
128
|
}
|
|
119
129
|
|
|
120
|
-
@
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
130
|
+
private Set<View> getRedactedViews(@NonNull final Activity activity) {
|
|
131
|
+
HashSet<View> redacted = new HashSet<>(RedactedViewManager.redactedViews.keySet());
|
|
132
|
+
if (delegate instanceof io.cobrowse.reactnative.CobrowseIO.RedactionDelegate) {
|
|
133
|
+
List<View> views = ((io.cobrowse.reactnative.CobrowseIO.RedactionDelegate) delegate).redactedViews(activity);
|
|
134
|
+
if (views != null) redacted.addAll(views);
|
|
135
|
+
}
|
|
136
|
+
return redacted;
|
|
124
137
|
}
|
|
125
138
|
|
|
126
139
|
@Override
|
|
127
|
-
public List<View> redactedViews(@NonNull final Activity activity)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
140
|
+
public List<View> redactedViews(@NonNull final Activity activity) {
|
|
141
|
+
final Set<View> redacted = this.getRedactedViews(activity);
|
|
142
|
+
final Set<View> unredacted = this.getUnredactedViews(activity);
|
|
143
|
+
|
|
144
|
+
// Project all unredactions to the root to get the full set of unredacted nodes
|
|
145
|
+
HashSet<View> projectedUnredacted = new HashSet<>();
|
|
146
|
+
for (View view : unredacted) {
|
|
147
|
+
projectedUnredacted.add(view);
|
|
148
|
+
projectedUnredacted.addAll(TreeUtils.allParents(view));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Work out which nodes are explicitly redacted but need to be unredacted
|
|
152
|
+
// due to a nested unredaction tag (these redactions will be moved towards the
|
|
153
|
+
// leaves of the view hierarchy instead)
|
|
154
|
+
HashSet<View> redactedProjectedUnredactions = new HashSet<>(redacted);
|
|
155
|
+
redactedProjectedUnredactions.retainAll(projectedUnredacted);
|
|
156
|
+
|
|
157
|
+
// Start to build the set of redactions we should actually apply. We start off with
|
|
158
|
+
// the set of explicitly redacted nodes
|
|
159
|
+
HashSet<View> toRedact = new HashSet<>(redacted);
|
|
160
|
+
|
|
161
|
+
// remove any redactions that have unredacted decendants (i.e. any that appear in the
|
|
162
|
+
// projection of the unredaction). These are the redacted nodes we need to move towards
|
|
163
|
+
// the leaves of the tree
|
|
164
|
+
toRedact.removeAll(redactedProjectedUnredactions);
|
|
165
|
+
|
|
166
|
+
// Work out the set of all nodes that are siblings of any projected unredacted node
|
|
167
|
+
HashSet<View> projectedUnredactionSiblings = new HashSet<>();
|
|
168
|
+
for (View view : projectedUnredacted) {
|
|
169
|
+
ViewParent parent = view.getParent();
|
|
170
|
+
if (parent instanceof ViewGroup)
|
|
171
|
+
projectedUnredactionSiblings.addAll(TreeUtils.directChildren((ViewGroup) parent));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Subtract the projected unredactions from the set of all siblings of projected
|
|
175
|
+
// unredactions. i.e. subtract things that should be definitely unredacted to leave
|
|
176
|
+
// a set we're not sure yet whether to redact or not
|
|
177
|
+
HashSet<View> potentiallyRedactedUnredactedSiblings = new HashSet<View>(projectedUnredactionSiblings);
|
|
178
|
+
potentiallyRedactedUnredactedSiblings.removeAll(projectedUnredacted);
|
|
179
|
+
|
|
180
|
+
// for each node we're not sure about yet, check if the closest redacted or unredacted ancestor
|
|
181
|
+
// is a redacted node, if so this descendant should also be redacted
|
|
182
|
+
for (View view : potentiallyRedactedUnredactedSiblings) {
|
|
183
|
+
View closest = TreeUtils.closest(view, new Predicate<View>() {
|
|
184
|
+
@Override
|
|
185
|
+
public boolean test(View view) {
|
|
186
|
+
return redacted.contains(view) || unredacted.contains(view);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
if (redacted.contains(closest)) toRedact.add(view);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Remove any empty ViewGroup from the redacted set, they're often used for wrapping
|
|
193
|
+
// or sizing other elements, and do not usually need to be redacted
|
|
194
|
+
// If it's absolutely necessary they are redacted, they can always be replaced with
|
|
195
|
+
// a <Redacted> tag instead
|
|
196
|
+
for (View v : new HashSet<>(toRedact)) {
|
|
197
|
+
if (v instanceof ViewGroup && ((ViewGroup) v).getChildCount() == 0) {
|
|
198
|
+
toRedact.remove(v);
|
|
149
199
|
}
|
|
200
|
+
}
|
|
150
201
|
|
|
151
|
-
|
|
152
|
-
// This should give us the set including unredacted nodes, their siblings,
|
|
153
|
-
// and all their parents.
|
|
154
|
-
for (View parent : unredactedParents) redacted.addAll(TreeUtils.directChildren(parent));
|
|
155
|
-
|
|
156
|
-
// Then we can subtract the set of unredacted parents to find just the
|
|
157
|
-
// set of unredacted nodes that are leaves of the parent subtree
|
|
158
|
-
redacted.removeAll(unredactedParents);
|
|
159
|
-
|
|
160
|
-
// Finally we can subtract the set of unredacted views to get the minimal
|
|
161
|
-
// set of redactions that will redact everything that's not explicitly unredacted
|
|
162
|
-
// whilst allowing the unredacted views to be visible
|
|
163
|
-
redacted.removeAll(unredacted);
|
|
164
|
-
|
|
165
|
-
// Remove any empty ReactViewGroup from the redacted set, they're often used for wrapping
|
|
166
|
-
// or sizing other elements, and do not usually need to be redacted
|
|
167
|
-
// If it's absolutely necessary they are redacted, they can always be replaced with
|
|
168
|
-
// a <Redacted> tag instead
|
|
169
|
-
for (View v : new HashSet<>(redacted))
|
|
170
|
-
if (v instanceof ReactViewGroup && ((ReactViewGroup) v).getChildCount() == 0)
|
|
171
|
-
redacted.remove(v);
|
|
172
|
-
|
|
173
|
-
// Any explicitly redacted views surrounded by <Redacted> tags take precedence, so
|
|
174
|
-
// re-add any tagged as such that the process above might have removed
|
|
175
|
-
redacted.addAll(RedactedViewManager.redactedViews.keySet());
|
|
176
|
-
|
|
177
|
-
return new ArrayList<>(redacted);
|
|
202
|
+
return new ArrayList<>(toRedact);
|
|
178
203
|
}
|
|
179
204
|
|
|
180
205
|
@ReactMethod
|
|
@@ -350,4 +375,5 @@ public class CobrowseIOModule extends ReactContextBaseJavaModule
|
|
|
350
375
|
public void accessibilityServiceIsRunning(final Promise promise) {
|
|
351
376
|
promise.resolve(CobrowseAccessibilityService.isRunning(getReactApplicationContext()));
|
|
352
377
|
}
|
|
378
|
+
|
|
353
379
|
}
|
|
@@ -4,11 +4,7 @@ import android.view.View;
|
|
|
4
4
|
import android.view.ViewGroup;
|
|
5
5
|
import android.view.ViewParent;
|
|
6
6
|
|
|
7
|
-
import com.facebook.react.ReactRootView;
|
|
8
|
-
import com.facebook.react.views.view.ReactViewGroup;
|
|
9
|
-
|
|
10
7
|
import java.util.ArrayList;
|
|
11
|
-
import java.util.Collections;
|
|
12
8
|
import java.util.HashSet;
|
|
13
9
|
import java.util.List;
|
|
14
10
|
import java.util.Set;
|
|
@@ -38,32 +34,10 @@ class TreeUtils {
|
|
|
38
34
|
return parents;
|
|
39
35
|
}
|
|
40
36
|
|
|
41
|
-
public static
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
reactParents.remove(v);
|
|
47
|
-
}
|
|
48
|
-
return reactParents;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
public static Set<View> findAllClosest(View root, Predicate<View> predicate) {
|
|
52
|
-
HashSet<View> found = new HashSet<>();
|
|
53
|
-
if (predicate.test(root)) found.add(root);
|
|
54
|
-
else {
|
|
55
|
-
for (View v : directChildren(root)) {
|
|
56
|
-
found.addAll(findAllClosest(v, predicate));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return found;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
public static boolean isReactView(View view) {
|
|
63
|
-
return view instanceof ReactRootView || view instanceof ReactViewGroup;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
public static boolean hasAnyParent(View node, Set<View> matches) {
|
|
67
|
-
return !Collections.disjoint(TreeUtils.allParents(node), matches);
|
|
37
|
+
public static View closest(View root, Predicate<View> predicate) {
|
|
38
|
+
if (predicate.test(root)) return root;
|
|
39
|
+
ViewParent parent = root.getParent();
|
|
40
|
+
if (parent instanceof ViewGroup) return closest((ViewGroup)parent, predicate);
|
|
41
|
+
return null;
|
|
68
42
|
}
|
|
69
43
|
}
|
|
@@ -11,7 +11,7 @@ Pod::Spec.new do |s|
|
|
|
11
11
|
s.homepage = package["homepage"]
|
|
12
12
|
s.source = { :git => 'https://github.com/cobrowseio/cobrowse-sdk-react-native.git' }
|
|
13
13
|
s.platform = :ios, '9.0'
|
|
14
|
-
s.dependency 'CobrowseIO/XCFramework', '2.
|
|
14
|
+
s.dependency 'CobrowseIO/XCFramework', '2.21.2'
|
|
15
15
|
s.dependency 'React'
|
|
16
16
|
s.source_files = 'ios/*.{h,m}'
|
|
17
17
|
end
|
package/ios/RCTCBIOTreeUtils.h
CHANGED
|
@@ -4,12 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
+(NSArray*) allParents: (UIView*) root;
|
|
6
6
|
|
|
7
|
-
+(
|
|
8
|
-
|
|
9
|
-
+(NSMutableSet*) findAllClosest: (BOOL (^)(UIView* view))predicate under: (UIView*) root;
|
|
10
|
-
|
|
11
|
-
+(bool) isReactView: (UIView*) view;
|
|
12
|
-
|
|
13
|
-
+(bool) hasAnyParent: (UIView*) view matches: (NSSet*) matches;
|
|
7
|
+
+(UIView*) closest: (BOOL (^)(UIView* view))predicate from: (UIView*) root;
|
|
14
8
|
|
|
15
9
|
@end
|
package/ios/RCTCBIOTreeUtils.m
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
#import "RCTCBIOTreeUtils.h"
|
|
2
|
-
#import <React/RCTRootView.h>
|
|
3
|
-
#import <React/RCTView.h>
|
|
4
2
|
|
|
5
3
|
@implementation RCTCBIOTreeUtils
|
|
6
4
|
|
|
@@ -14,34 +12,10 @@
|
|
|
14
12
|
return parents;
|
|
15
13
|
}
|
|
16
14
|
|
|
17
|
-
+(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if ([self isReactView:view]) break;
|
|
22
|
-
[reactParents removeObject: view];
|
|
23
|
-
}
|
|
24
|
-
return reactParents;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
+(NSMutableSet*) findAllClosest: (BOOL (^)(UIView* view))predicate under: (UIView*) root {
|
|
28
|
-
NSMutableSet* found = [NSMutableSet set];
|
|
29
|
-
if (predicate(root)) [found addObject:root];
|
|
30
|
-
else {
|
|
31
|
-
for (UIView* child in root.subviews) {
|
|
32
|
-
[found addObjectsFromArray: [self findAllClosest:predicate under:child].allObjects];
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return found;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
+(bool) isReactView: (UIView*) view {
|
|
39
|
-
return [view isKindOfClass:RCTRootView.class] || [view isKindOfClass: RCTView.class];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
+(bool) hasAnyParent: (UIView*) view matches: (NSSet*) matches {
|
|
43
|
-
NSSet* parents = [NSSet setWithArray: [self allParents:view]];
|
|
44
|
-
return [parents intersectsSet: matches];
|
|
15
|
+
+(UIView*) closest: (BOOL (^)(UIView* view))predicate from: (UIView*) root {
|
|
16
|
+
if (predicate(root)) return root;
|
|
17
|
+
else if (!root.superview) return nil;
|
|
18
|
+
else return [self closest:predicate from:root.superview];
|
|
45
19
|
}
|
|
46
20
|
|
|
47
21
|
@end
|
package/ios/RCTCobrowseIO.h
CHANGED
|
@@ -4,6 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
@import CobrowseIO;
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
@protocol RCTCobrowseIODelegate <NSObject>
|
|
9
|
+
|
|
10
|
+
@optional
|
|
11
|
+
|
|
12
|
+
-(nonnull NSArray<UIView*>*) cobrowseRedactedViewsForViewController: (nonnull UIViewController*) vc;
|
|
13
|
+
-(nonnull NSArray<UIView*>*) cobrowseUnredactedViewsForViewController: (nonnull UIViewController*) vc;
|
|
14
|
+
|
|
15
|
+
@end
|
|
16
|
+
|
|
17
|
+
|
|
7
18
|
@interface RCTCobrowseIO: RCTEventEmitter <RCTBridgeModule, CobrowseIODelegate>
|
|
8
19
|
|
|
20
|
+
@property (class, nullable) id<RCTCobrowseIODelegate> delegate;
|
|
21
|
+
|
|
9
22
|
@end
|
|
23
|
+
|
package/ios/RCTCobrowseIO.m
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
#define SESSION_ENDED "session.ended"
|
|
15
15
|
#define SESSION_REQUESTED "session.requested"
|
|
16
16
|
|
|
17
|
+
static id<RCTCobrowseIODelegate> _Nullable _delegate;
|
|
18
|
+
|
|
17
19
|
@import CobrowseIO;
|
|
18
20
|
|
|
19
21
|
@implementation RCTCobrowseIO {
|
|
@@ -32,6 +34,14 @@ RCT_EXPORT_MODULE();
|
|
|
32
34
|
return self;
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
+(id<RCTCobrowseIODelegate>)delegate {
|
|
38
|
+
return _delegate;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
+(void)setDelegate:(id<RCTCobrowseIODelegate>)delegate {
|
|
42
|
+
_delegate = delegate;
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
+ (BOOL)requiresMainQueueSetup {
|
|
36
46
|
return YES;
|
|
37
47
|
}
|
|
@@ -56,7 +66,15 @@ RCT_EXPORT_MODULE();
|
|
|
56
66
|
if (hasListeners) [self sendEventWithName:@SESSION_LOADED body:[session toDict]];
|
|
57
67
|
}
|
|
58
68
|
|
|
59
|
-
-(NSSet<UIView*>*)
|
|
69
|
+
-(NSSet<UIView*>*) redactedViews: (UIViewController*) vc {
|
|
70
|
+
NSMutableSet* views = [CBIOCobrowseRedactedManager.redactedViews mutableCopy];
|
|
71
|
+
if ([RCTCobrowseIO.delegate respondsToSelector:@selector(cobrowseRedactedViewsForViewController:)]) {
|
|
72
|
+
[views addObjectsFromArray: [RCTCobrowseIO.delegate cobrowseRedactedViewsForViewController:vc]];
|
|
73
|
+
}
|
|
74
|
+
return views;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
-(NSSet<UIView*>*) unredactedViews: (UIViewController*) vc {
|
|
60
78
|
NSMutableSet* views = [NSMutableSet set];
|
|
61
79
|
@synchronized(unredactedTags) {
|
|
62
80
|
for (id tag in unredactedTags) {
|
|
@@ -64,6 +82,9 @@ RCT_EXPORT_MODULE();
|
|
|
64
82
|
if (v != nil) [views addObject:v];
|
|
65
83
|
}
|
|
66
84
|
}
|
|
85
|
+
if ([RCTCobrowseIO.delegate respondsToSelector:@selector(cobrowseUnredactedViewsForViewController:)]) {
|
|
86
|
+
[views addObjectsFromArray: [RCTCobrowseIO.delegate cobrowseUnredactedViewsForViewController:vc]];
|
|
87
|
+
}
|
|
67
88
|
return views;
|
|
68
89
|
}
|
|
69
90
|
|
|
@@ -85,59 +106,60 @@ RCT_EXPORT_MODULE();
|
|
|
85
106
|
}
|
|
86
107
|
|
|
87
108
|
-(NSArray<UIView *> *)cobrowseRedactedViewsForViewController:(UIViewController *)vc {
|
|
88
|
-
|
|
89
|
-
NSSet* unredacted = self
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
// to instances where windows that do not contain a RN context could
|
|
94
|
-
// not be unredacted.
|
|
95
|
-
// A simple example of this is the overlay window that cobrowse adds to
|
|
96
|
-
// render its annotations. This window sits on top of all the other windows
|
|
97
|
-
// and would always be redacted, effecivley redacting the entire screen all
|
|
98
|
-
// the time.
|
|
99
|
-
// To get around this, we only redact views below RCTViews or RCTRootViews
|
|
100
|
-
// (not inclusive), as then it's always possible to add an unredact()'ed component
|
|
101
|
-
// around a subview in the react tree to make parent visible.
|
|
102
|
-
NSSet* rootViews = [RCTCBIOTreeUtils findAllClosest:^BOOL(UIView *view) {
|
|
103
|
-
return [RCTCBIOTreeUtils isReactView: view];
|
|
104
|
-
} under: vc.view];
|
|
105
|
-
for (UIView* v in rootViews) [redacted addObjectsFromArray: v.subviews];
|
|
106
|
-
|
|
107
|
-
// Now we can actually start working out what should be unredacted
|
|
108
|
-
// First work out the set of all parents of unredact()'ed nodes
|
|
109
|
-
// that are inside a react view (ignoring any nested unredaction)
|
|
110
|
-
NSMutableSet* unredactedParents = [NSMutableSet set];
|
|
109
|
+
NSSet* redacted = [self redactedViews: vc];
|
|
110
|
+
NSSet* unredacted = [self unredactedViews: vc];
|
|
111
|
+
|
|
112
|
+
// Project all unredactions to the root to get the full set of unredacted nodes
|
|
113
|
+
NSMutableSet* projectedUnredacted = [NSMutableSet set];
|
|
111
114
|
for (id view in unredacted) {
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
[projectedUnredacted addObject:view];
|
|
116
|
+
[projectedUnredacted addObjectsFromArray: [RCTCBIOTreeUtils allParents: view]];
|
|
114
117
|
}
|
|
115
|
-
// Then work out the set of all direct children of any unredacted parents
|
|
116
|
-
// This should give us the set including unredacted nodes, their siblings,
|
|
117
|
-
// and all their parents.
|
|
118
|
-
for (UIView* parent in unredactedParents) [redacted addObjectsFromArray: parent.subviews];
|
|
119
|
-
|
|
120
|
-
// Then we can subtract the set of unredacted parents to find just the
|
|
121
|
-
// set of unredacted nodes that are leaves of the parent subtree
|
|
122
|
-
for (id v in unredactedParents) [redacted removeObject:v];
|
|
123
|
-
|
|
124
|
-
// Finally we can subtract the set of unredacted views to get the minimal
|
|
125
|
-
// set of redactions that will redact everything that's not explicitly unredacted
|
|
126
|
-
// whilst allowing the unredacted views to be visible
|
|
127
|
-
for (UIView* v in unredacted) [redacted removeObject:v];
|
|
128
118
|
|
|
119
|
+
// Work out which nodes are explicitly redacted but need to be unredacted
|
|
120
|
+
// due to a nested unredaction tag (these redactions will be moved towards the
|
|
121
|
+
// leaves of the view hierarchy instead)
|
|
122
|
+
NSMutableSet* redactedProjectedUnredactions = [redacted mutableCopy];
|
|
123
|
+
[redactedProjectedUnredactions intersectSet: projectedUnredacted];
|
|
124
|
+
|
|
125
|
+
// Start to build the set of redactions we should actually apply. We start off with
|
|
126
|
+
// the set of explicitly redacted nodes
|
|
127
|
+
NSMutableSet* toRedact = [redacted mutableCopy];
|
|
128
|
+
|
|
129
|
+
// remove any redactions that have unredacted decendants (i.e. any that appear in the
|
|
130
|
+
// projection of the unredaction). These are the redacted nodes we need to move towards
|
|
131
|
+
// the leaves of the tree
|
|
132
|
+
[toRedact minusSet: redactedProjectedUnredactions];
|
|
133
|
+
|
|
134
|
+
// Work out the set of all nodes that are siblings of any projected unredacted node
|
|
135
|
+
NSMutableSet* projectedUnredactionSiblings = [NSMutableSet set];
|
|
136
|
+
for (UIView* view in projectedUnredacted) {
|
|
137
|
+
[projectedUnredactionSiblings addObjectsFromArray: view.superview.subviews];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Subtract the projected unredactions from the set of all siblings of projected
|
|
141
|
+
// unredactions. i.e. subtract things that should be definitely unredacted to leave
|
|
142
|
+
// a set we're not sure yet whether to redact or not
|
|
143
|
+
NSMutableSet* potentiallyRedactedUnredactedSiblings = [projectedUnredactionSiblings mutableCopy];
|
|
144
|
+
[potentiallyRedactedUnredactedSiblings minusSet:projectedUnredacted];
|
|
145
|
+
|
|
146
|
+
// for each node we're not sure about yet, check if the closest redacted or unredacted ancestor
|
|
147
|
+
// is a redacted node, if so this descendant should also be redacted
|
|
148
|
+
for (UIView* view in potentiallyRedactedUnredactedSiblings) {
|
|
149
|
+
UIView* closest = [RCTCBIOTreeUtils closest:^BOOL(UIView *v) {
|
|
150
|
+
return [redacted containsObject:v] || [unredacted containsObject:v];
|
|
151
|
+
} from: view];
|
|
152
|
+
if ([redacted containsObject: closest]) [toRedact addObject: view];
|
|
153
|
+
}
|
|
154
|
+
|
|
129
155
|
// Remove any empty RCTViews from the redacted set, they're often used for wrapping
|
|
130
156
|
// or sizing other elements, and do not usually need to be redacted
|
|
131
157
|
// If it's absolutely necessary they are redacted, they can always be replaced with
|
|
132
158
|
// a <Redacted> tag instead
|
|
133
|
-
for (UIView* v in [
|
|
134
|
-
if ([v isKindOfClass:RCTView.class] && v.subviews.count == 0) [
|
|
135
|
-
|
|
136
|
-
// Any explicitly redacted views surroudned by <Redacted> tags take precedence, so
|
|
137
|
-
// re-add any tagged as such that the process above might have removed
|
|
138
|
-
for (id v in CBIOCobrowseRedactedManager.redactedViews.allObjects) [redacted addObject: v];
|
|
159
|
+
for (UIView* v in [toRedact copy])
|
|
160
|
+
if ([v isKindOfClass:RCTView.class] && v.subviews.count == 0) [toRedact removeObject: v];
|
|
139
161
|
|
|
140
|
-
return
|
|
162
|
+
return toRedact.allObjects;
|
|
141
163
|
}
|
|
142
164
|
|
|
143
165
|
- (bool) cobrowseShouldCaptureWindow:(UIWindow *)window {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cobrowse-sdk-react-native",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.0",
|
|
4
4
|
"description": "Cobrowse SDK for React Native",
|
|
5
5
|
"main": "js/index.js",
|
|
6
6
|
"types": "ts/index.d.ts",
|
|
@@ -27,17 +27,17 @@
|
|
|
27
27
|
],
|
|
28
28
|
"license": "Apache-2.0",
|
|
29
29
|
"optionalDependencies": {
|
|
30
|
-
"@types/react": "^
|
|
31
|
-
"@types/react-native": "^0.
|
|
30
|
+
"@types/react": "^18.0.18",
|
|
31
|
+
"@types/react-native": "^0.69.6"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"lodash": "^4.17.21"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@types/react": "^
|
|
38
|
-
"@types/react-native": "^0.
|
|
39
|
-
"nodemon": "^2.0.
|
|
37
|
+
"@types/react": "^18.0.18",
|
|
38
|
+
"@types/react-native": "^0.69.6",
|
|
39
|
+
"nodemon": "^2.0.19",
|
|
40
40
|
"ts-standard": "^11.0.0",
|
|
41
|
-
"typescript": "^4.
|
|
41
|
+
"typescript": "^4.8.2"
|
|
42
42
|
}
|
|
43
43
|
}
|