cobrowse-sdk-react-native 2.13.0 → 2.15.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/.github/workflows/lint.yaml +22 -0
- package/.github/workflows/release.yml +23 -0
- package/.vscode/settings.json +7 -0
- package/DEPLOYMENT.md +7 -0
- package/README.md +6 -33
- package/android/build.gradle +1 -1
- package/android/src/main/java/io/cobrowse/reactnative/CobrowseIOCommonDelegates.java +14 -0
- package/android/src/main/java/io/cobrowse/reactnative/CobrowseIOModule.java +210 -346
- package/android/src/main/java/io/cobrowse/reactnative/CommonDelegates.java +133 -0
- package/android/src/main/java/io/cobrowse/reactnative/Conversion.java +73 -42
- package/android/src/main/java/io/cobrowse/reactnative/DynamicSessionControlInvocationHandler.java +31 -0
- package/cobrowse-sdk-react-native.podspec +1 -1
- package/ios/CBIOBroadcastPickerView.h +5 -0
- package/ios/CBIOBroadcastPickerView.m +52 -0
- package/ios/CBIORCTUtil.h +1 -0
- package/ios/CBIORCTUtil.m +10 -0
- package/ios/CBIOSession+Bridging.m +1 -0
- package/ios/RCTCobrowseIO.m +32 -72
- package/js/CBIOBroadcastPickerView.js +11 -0
- package/js/CobrowseIO.js +22 -1
- package/js/CobrowseView.js +2 -1
- package/js/Session.js +4 -0
- package/js/Unredacted.js +2 -2
- package/js/index.js +2 -0
- package/js/useSession.js +33 -0
- package/package.json +18 -10
- package/ts/CBIOBroadcastPickerView.d.ts +8 -0
- package/ts/CobrowseView.d.ts +7 -7
- package/ts/Redacted.d.ts +3 -3
- package/ts/Session.d.ts +3 -1
- package/ts/Unredacted.d.ts +3 -2
- package/ts/index.d.ts +1 -0
- package/ts/useSession.d.ts +3 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
package io.cobrowse.reactnative;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.util.Log;
|
|
5
|
+
import android.view.View;
|
|
6
|
+
import android.view.ViewGroup;
|
|
7
|
+
import android.view.ViewParent;
|
|
8
|
+
|
|
9
|
+
import androidx.annotation.NonNull;
|
|
10
|
+
import androidx.core.util.Predicate;
|
|
11
|
+
|
|
12
|
+
import com.facebook.react.bridge.Promise;
|
|
13
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
14
|
+
import com.facebook.react.bridge.ReadableArray;
|
|
15
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
16
|
+
import com.facebook.react.uimanager.NativeViewHierarchyManager;
|
|
17
|
+
import com.facebook.react.uimanager.UIBlock;
|
|
18
|
+
import com.facebook.react.uimanager.UIManagerModule;
|
|
19
|
+
|
|
20
|
+
import java.util.ArrayList;
|
|
21
|
+
import java.util.HashSet;
|
|
22
|
+
import java.util.List;
|
|
23
|
+
import java.util.Set;
|
|
24
|
+
|
|
25
|
+
import io.cobrowse.Session;
|
|
26
|
+
|
|
27
|
+
class CommonDelegates implements CobrowseIOCommonDelegates {
|
|
28
|
+
|
|
29
|
+
private static final String SESSION_LOADED = "session.loaded";
|
|
30
|
+
private static final String SESSION_UPDATED = "session.updated";
|
|
31
|
+
private static final String SESSION_ENDED = "session.ended";
|
|
32
|
+
private static final String SESSION_REQUESTED = "session.requested";
|
|
33
|
+
|
|
34
|
+
private final HashSet<Integer> unredactedTags = new HashSet<>();
|
|
35
|
+
private ReactApplicationContext reactApplicationContext;
|
|
36
|
+
private NativeViewHierarchyManager nodeManager;
|
|
37
|
+
|
|
38
|
+
CommonDelegates(ReactApplicationContext context) {
|
|
39
|
+
this.reactApplicationContext = context;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@Override
|
|
43
|
+
public void handleFullDeviceRequest(@NonNull Activity activity, @NonNull Session session) {
|
|
44
|
+
// no-op, this will be handed on the JS side via an "updated" event handler
|
|
45
|
+
// this stub just disables the default native prompt in the SDK
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@Override
|
|
49
|
+
public List<View> redactedViews(@NonNull Activity activity) {
|
|
50
|
+
HashSet<View> redacted = new HashSet<>(RedactedViewManager.redactedViews.keySet());
|
|
51
|
+
if (CobrowseIOModule.delegate instanceof io.cobrowse.reactnative.CobrowseIO.RedactionDelegate) {
|
|
52
|
+
List<View> views = ((io.cobrowse.reactnative.CobrowseIO.RedactionDelegate) CobrowseIOModule.delegate).redactedViews(activity);
|
|
53
|
+
if (views != null) redacted.addAll(views);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return new ArrayList<>(redacted);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@Override
|
|
60
|
+
public List<View> unredactedViews(@NonNull Activity activity) {
|
|
61
|
+
synchronized (unredactedTags) {
|
|
62
|
+
HashSet<View> unredacted = new HashSet<>();
|
|
63
|
+
for (Integer i : unredactedTags) {
|
|
64
|
+
try {
|
|
65
|
+
unredacted.add(nodeManager.resolveView(i));
|
|
66
|
+
} catch (Exception e) {
|
|
67
|
+
Log.i("CobrowseIO", "Failed to find unredacted view for tag " + i + ", error = " + e.getMessage());
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (CobrowseIOModule.delegate instanceof io.cobrowse.reactnative.CobrowseIO.RedactionDelegate) {
|
|
71
|
+
List<View> views = ((io.cobrowse.reactnative.CobrowseIO.RedactionDelegate) CobrowseIOModule.delegate).unredactedViews(activity);
|
|
72
|
+
if (views != null) unredacted.addAll(views);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return new ArrayList<>(unredacted);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@Override
|
|
80
|
+
public void handleRemoteControlRequest(@NonNull Activity activity, @NonNull Session session) {
|
|
81
|
+
// no-op, this will be handed on the JS side via an "updated" event handler
|
|
82
|
+
// this stub just disables the default native prompt in the SDK
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@Override
|
|
86
|
+
public void sessionDidLoad(@NonNull Session session) {
|
|
87
|
+
reactApplicationContext
|
|
88
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
89
|
+
.emit(SESSION_LOADED, Conversion.convert(session));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@Override
|
|
93
|
+
public void handleSessionRequest(@NonNull Activity activity, @NonNull Session session) {
|
|
94
|
+
reactApplicationContext
|
|
95
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
96
|
+
.emit(SESSION_REQUESTED, Conversion.convert(session));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@Override
|
|
100
|
+
public void sessionDidUpdate(@NonNull Session session) {
|
|
101
|
+
reactApplicationContext
|
|
102
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
103
|
+
.emit(SESSION_UPDATED, Conversion.convert(session));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@Override
|
|
107
|
+
public void sessionDidEnd(@NonNull Session session) {
|
|
108
|
+
reactApplicationContext
|
|
109
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
110
|
+
.emit(SESSION_ENDED, Conversion.convert(session));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public void findNodeManager() {
|
|
114
|
+
if (nodeManager != null) return;
|
|
115
|
+
final UIManagerModule uiManager = reactApplicationContext.getNativeModule(UIManagerModule.class);
|
|
116
|
+
assert uiManager != null;
|
|
117
|
+
uiManager.prependUIBlock(new UIBlock() {
|
|
118
|
+
@Override
|
|
119
|
+
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
|
|
120
|
+
nodeManager = nativeViewHierarchyManager;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public void setUnredactedTags(final ReadableArray reactTags, final Promise promise) {
|
|
126
|
+
synchronized (unredactedTags) {
|
|
127
|
+
unredactedTags.clear();
|
|
128
|
+
for (int i = 0; i < reactTags.size(); i++)
|
|
129
|
+
unredactedTags.add(reactTags.getInt(i));
|
|
130
|
+
promise.resolve(null);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -6,62 +6,93 @@ import com.facebook.react.bridge.WritableMap;
|
|
|
6
6
|
import io.cobrowse.Agent;
|
|
7
7
|
import io.cobrowse.Session;
|
|
8
8
|
import io.cobrowse.Session.RemoteControlState;
|
|
9
|
+
import io.cobrowse.Session.FullDeviceState;
|
|
9
10
|
|
|
10
11
|
final class Conversion {
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
static WritableMap convert(Session session) {
|
|
14
|
+
WritableMap map = Arguments.createMap();
|
|
15
|
+
if (session == null) return null;
|
|
16
|
+
map.putString("code", session.code());
|
|
17
|
+
map.putString("state", session.state());
|
|
18
|
+
map.putString("id", session.id());
|
|
19
|
+
map.putBoolean("full_device", session.fullDevice());
|
|
20
|
+
map.putString("full_device_state", Conversion.fullDeviceState(session.fullDeviceState()));
|
|
21
|
+
map.putString("remote_control", Conversion.remoteControl(session.remoteControl()));
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
Agent agent = session.agent();
|
|
24
|
+
if (agent != null) {
|
|
25
|
+
WritableMap agentMap = Arguments.createMap();
|
|
26
|
+
agentMap.putString("name", agent.name);
|
|
27
|
+
agentMap.putString("id", agent.id);
|
|
28
|
+
map.putMap("agent", agentMap);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return map;
|
|
32
|
+
}
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
static String convert(Error error) {
|
|
35
|
+
if (error == null) return null;
|
|
36
|
+
return error.getMessage();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static String remoteControl(RemoteControlState state) {
|
|
40
|
+
switch (state) {
|
|
41
|
+
case On:
|
|
42
|
+
return "on";
|
|
43
|
+
case Requested:
|
|
44
|
+
return "requested";
|
|
45
|
+
case Rejected:
|
|
46
|
+
return "rejected";
|
|
47
|
+
case Off:
|
|
48
|
+
return "off";
|
|
30
49
|
}
|
|
31
50
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
return "off";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static RemoteControlState remoteControl(String state) {
|
|
55
|
+
switch (state) {
|
|
56
|
+
case "on":
|
|
57
|
+
return RemoteControlState.On;
|
|
58
|
+
case "requested":
|
|
59
|
+
return RemoteControlState.Requested;
|
|
60
|
+
case "rejected":
|
|
61
|
+
return RemoteControlState.Rejected;
|
|
62
|
+
case "off":
|
|
63
|
+
return RemoteControlState.Off;
|
|
35
64
|
}
|
|
36
65
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
case On:
|
|
40
|
-
return "on";
|
|
41
|
-
case Requested:
|
|
42
|
-
return "requested";
|
|
43
|
-
case Rejected:
|
|
44
|
-
return "rejected";
|
|
45
|
-
case Off:
|
|
46
|
-
return "off";
|
|
47
|
-
}
|
|
66
|
+
return RemoteControlState.Off;
|
|
67
|
+
}
|
|
48
68
|
|
|
69
|
+
static String fullDeviceState(FullDeviceState state) {
|
|
70
|
+
switch (state) {
|
|
71
|
+
case On:
|
|
72
|
+
return "on";
|
|
73
|
+
case Requested:
|
|
74
|
+
return "requested";
|
|
75
|
+
case Rejected:
|
|
76
|
+
return "rejected";
|
|
77
|
+
case Off:
|
|
49
78
|
return "off";
|
|
50
79
|
}
|
|
51
80
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
case "on":
|
|
55
|
-
return RemoteControlState.On;
|
|
56
|
-
case "requested":
|
|
57
|
-
return RemoteControlState.Requested;
|
|
58
|
-
case "rejected":
|
|
59
|
-
return RemoteControlState.Rejected;
|
|
60
|
-
case "off":
|
|
61
|
-
return RemoteControlState.Off;
|
|
62
|
-
}
|
|
81
|
+
return "off";
|
|
82
|
+
}
|
|
63
83
|
|
|
64
|
-
|
|
84
|
+
static FullDeviceState fullDeviceState(String state) {
|
|
85
|
+
switch (state) {
|
|
86
|
+
case "on":
|
|
87
|
+
return FullDeviceState.On;
|
|
88
|
+
case "requested":
|
|
89
|
+
return FullDeviceState.Requested;
|
|
90
|
+
case "rejected":
|
|
91
|
+
return FullDeviceState.Rejected;
|
|
92
|
+
case "off":
|
|
93
|
+
return FullDeviceState.Off;
|
|
65
94
|
}
|
|
66
95
|
|
|
96
|
+
return FullDeviceState.Off;
|
|
97
|
+
}
|
|
67
98
|
}
|
package/android/src/main/java/io/cobrowse/reactnative/DynamicSessionControlInvocationHandler.java
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
package io.cobrowse.reactnative;
|
|
2
|
+
|
|
3
|
+
import java.lang.reflect.InvocationHandler;
|
|
4
|
+
import java.lang.reflect.Method;
|
|
5
|
+
import java.util.HashMap;
|
|
6
|
+
import java.util.Map;
|
|
7
|
+
|
|
8
|
+
class DynamicSessionControlInvocationHandler implements InvocationHandler {
|
|
9
|
+
private CobrowseIOCommonDelegates delegates;
|
|
10
|
+
private final Map<String, Method> methods = new HashMap<>();
|
|
11
|
+
|
|
12
|
+
DynamicSessionControlInvocationHandler(CobrowseIOCommonDelegates delegates) {
|
|
13
|
+
this.delegates = delegates;
|
|
14
|
+
|
|
15
|
+
for(Method method: delegates.getClass().getDeclaredMethods()) {
|
|
16
|
+
this.methods.put(method.getName(), method);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Override
|
|
21
|
+
public Object invoke(Object proxy, Method method, Object[] args)
|
|
22
|
+
throws Throwable {
|
|
23
|
+
|
|
24
|
+
if (method.getName() == "showSessionControls" || method.getName() == "hideSessionControls") {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
Object result = methods.get(method.getName()).invoke(delegates, args);
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -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, '11.0'
|
|
14
|
-
s.dependency 'CobrowseIO/XCFramework', '2.
|
|
14
|
+
s.dependency 'CobrowseIO/XCFramework', '2.25.0'
|
|
15
15
|
s.dependency 'React'
|
|
16
16
|
s.source_files = 'ios/*.{h,m}'
|
|
17
17
|
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#import <ReplayKit/ReplayKit.h>
|
|
2
|
+
#import "CBIOBroadcastPickerView.h"
|
|
3
|
+
|
|
4
|
+
@import CobrowseIO;
|
|
5
|
+
|
|
6
|
+
@implementation CBIOBroadcastPickerView
|
|
7
|
+
|
|
8
|
+
RCT_EXPORT_MODULE(CBIOBroadcastPickerView)
|
|
9
|
+
|
|
10
|
+
- (UIView *)view
|
|
11
|
+
{
|
|
12
|
+
NSString* broadcastExtension = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CBIOBroadcastExtension"];
|
|
13
|
+
NSString* warningMsg = @"";
|
|
14
|
+
|
|
15
|
+
if (!broadcastExtension) {
|
|
16
|
+
warningMsg = @"CBIOBroadcastExtension not set on Info.plist.";
|
|
17
|
+
} else if (@available(iOS 12.0, *)) {
|
|
18
|
+
RPSystemBroadcastPickerView* systemPicker = [[RPSystemBroadcastPickerView alloc] initWithFrame: CGRectMake(0, 0, 50, 50)];
|
|
19
|
+
systemPicker.showsMicrophoneButton = NO;
|
|
20
|
+
systemPicker.preferredExtension = broadcastExtension;
|
|
21
|
+
|
|
22
|
+
return systemPicker;
|
|
23
|
+
} else {
|
|
24
|
+
// Fallback on earlier versions
|
|
25
|
+
warningMsg = @"This version of iOS doesn't support RPSystemBroadcastPickerView.";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
RCTLogWarn(@"%@", warningMsg);
|
|
29
|
+
return [[UIView alloc] initWithFrame: CGRectMake(0, 0, 0, 0)];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
RCT_CUSTOM_VIEW_PROPERTY(buttonColor, NSString, UIView)
|
|
33
|
+
{
|
|
34
|
+
UIButton* button = view.subviews.firstObject;
|
|
35
|
+
if ([button isKindOfClass:UIButton.class]) button.imageView.tintColor = [self hexStringToColor:json];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
- hexStringToColor:(NSString *)stringToConvert
|
|
39
|
+
{
|
|
40
|
+
NSString *noHashString = [stringToConvert stringByReplacingOccurrencesOfString:@"#" withString:@""];
|
|
41
|
+
NSScanner *stringScanner = [NSScanner scannerWithString:noHashString];
|
|
42
|
+
|
|
43
|
+
unsigned hex;
|
|
44
|
+
if (![stringScanner scanHexInt:&hex]) return nil;
|
|
45
|
+
int r = (hex >> 16) & 0xFF;
|
|
46
|
+
int g = (hex >> 8) & 0xFF;
|
|
47
|
+
int b = (hex) & 0xFF;
|
|
48
|
+
|
|
49
|
+
return [UIColor colorWithRed:r / 255.0f green:g / 255.0f blue:b / 255.0f alpha:1.0f];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@end
|
package/ios/CBIORCTUtil.h
CHANGED
package/ios/CBIORCTUtil.m
CHANGED
|
@@ -12,4 +12,14 @@
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
+ (NSString *)fullDeviceState:(CBIOFullDeviceState)state {
|
|
16
|
+
switch(state) {
|
|
17
|
+
case kCBIOFullDeviceStateOn: return @"on";
|
|
18
|
+
case kCBIOFullDeviceStateOff: return @"off";
|
|
19
|
+
case kCBIOFullDeviceStateRejected: return @"rejected";
|
|
20
|
+
case kCBIOFullDeviceStateRequested: return @"requested";
|
|
21
|
+
default: return @"off";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
15
25
|
@end
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
@"code": self.code ? self.code : NSNull.null,
|
|
11
11
|
@"state": self.state ? self.state : NSNull.null,
|
|
12
12
|
@"full_device": @(self.fullDevice),
|
|
13
|
+
@"full_device_state": [CBIORCTUtil fullDeviceState: self.fullDeviceState],
|
|
13
14
|
@"remote_control": [CBIORCTUtil remoteControl: self.remoteControl],
|
|
14
15
|
@"agent": self.hasAgent ? @{
|
|
15
16
|
@"name": self.agent.name,
|
package/ios/RCTCobrowseIO.m
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
#import <React/RCTUIManager.h>
|
|
9
9
|
#import "RCTCobrowseIO.h"
|
|
10
10
|
#import "RCTCBIOTreeUtils.h"
|
|
11
|
+
#import <objc/runtime.h>
|
|
11
12
|
|
|
12
13
|
#define SESSION_LOADED "session.loaded"
|
|
13
14
|
#define SESSION_UPDATED "session.updated"
|
|
@@ -47,7 +48,7 @@ RCT_EXPORT_MODULE();
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
-(dispatch_queue_t) methodQueue {
|
|
50
|
-
|
|
51
|
+
return dispatch_get_main_queue();
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
-(void)startObserving {
|
|
@@ -66,28 +67,6 @@ RCT_EXPORT_MODULE();
|
|
|
66
67
|
if (hasListeners) [self sendEventWithName:@SESSION_LOADED body:[session toDict]];
|
|
67
68
|
}
|
|
68
69
|
|
|
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 {
|
|
78
|
-
NSMutableSet* views = [NSMutableSet set];
|
|
79
|
-
@synchronized(unredactedTags) {
|
|
80
|
-
for (id tag in unredactedTags) {
|
|
81
|
-
UIView* v = [self.bridge.uiManager viewForReactTag: tag];
|
|
82
|
-
if (v != nil) [views addObject:v];
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
if ([RCTCobrowseIO.delegate respondsToSelector:@selector(cobrowseUnredactedViewsForViewController:)]) {
|
|
86
|
-
[views addObjectsFromArray: [RCTCobrowseIO.delegate cobrowseUnredactedViewsForViewController:vc]];
|
|
87
|
-
}
|
|
88
|
-
return views;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
70
|
-(void)cobrowseSessionDidUpdate:(CBIOSession *)session {
|
|
92
71
|
if (hasListeners) [self sendEventWithName:@SESSION_UPDATED body:[session toDict]];
|
|
93
72
|
}
|
|
@@ -106,60 +85,27 @@ RCT_EXPORT_MODULE();
|
|
|
106
85
|
}
|
|
107
86
|
|
|
108
87
|
-(NSArray<UIView *> *)cobrowseRedactedViewsForViewController:(UIViewController *)vc {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// Project all unredactions to the root to get the full set of unredacted nodes
|
|
113
|
-
NSMutableSet* projectedUnredacted = [NSMutableSet set];
|
|
114
|
-
for (id view in unredacted) {
|
|
115
|
-
[projectedUnredacted addObject:view];
|
|
116
|
-
[projectedUnredacted addObjectsFromArray: [RCTCBIOTreeUtils allParents: view]];
|
|
88
|
+
NSMutableSet* views = [CBIOCobrowseRedactedManager.redactedViews mutableCopy];
|
|
89
|
+
if ([RCTCobrowseIO.delegate respondsToSelector:@selector(cobrowseRedactedViewsForViewController:)]) {
|
|
90
|
+
[views addObjectsFromArray: [RCTCobrowseIO.delegate cobrowseRedactedViewsForViewController:vc]];
|
|
117
91
|
}
|
|
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
92
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
93
|
+
return [views allObjects];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
- (NSArray<UIView *> *)cobrowseUnredactedViewsForViewController:(UIViewController *)vc {
|
|
97
|
+
NSMutableSet* views = [NSMutableSet set];
|
|
98
|
+
@synchronized(unredactedTags) {
|
|
99
|
+
for (id tag in unredactedTags) {
|
|
100
|
+
UIView* v = [self.bridge.uiManager viewForReactTag: tag];
|
|
101
|
+
if (v != nil) [views addObject:v];
|
|
102
|
+
}
|
|
138
103
|
}
|
|
139
|
-
|
|
140
|
-
|
|
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];
|
|
104
|
+
if ([RCTCobrowseIO.delegate respondsToSelector:@selector(cobrowseUnredactedViewsForViewController:)]) {
|
|
105
|
+
[views addObjectsFromArray: [RCTCobrowseIO.delegate cobrowseUnredactedViewsForViewController:vc]];
|
|
153
106
|
}
|
|
154
107
|
|
|
155
|
-
|
|
156
|
-
// or sizing other elements, and do not usually need to be redacted
|
|
157
|
-
// If it's absolutely necessary they are redacted, they can always be replaced with
|
|
158
|
-
// a <Redacted> tag instead
|
|
159
|
-
for (UIView* v in [toRedact copy])
|
|
160
|
-
if ([v isKindOfClass:RCTView.class] && v.subviews.count == 0) [toRedact removeObject: v];
|
|
161
|
-
|
|
162
|
-
return toRedact.allObjects;
|
|
108
|
+
return [views allObjects];
|
|
163
109
|
}
|
|
164
110
|
|
|
165
111
|
- (bool) cobrowseShouldCaptureWindow:(UIWindow *)window {
|
|
@@ -280,4 +226,18 @@ RCT_REMAP_METHOD(updateSession,
|
|
|
280
226
|
}];
|
|
281
227
|
}
|
|
282
228
|
|
|
229
|
+
void noopImplementation(id self, SEL _cmd, CBIOSession* session) {
|
|
230
|
+
// no-op function which will overwrite the default full device UI when enabled so
|
|
231
|
+
// it can be handled on RN side
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
RCT_EXPORT_METHOD(overwriteFullControlUI) {
|
|
235
|
+
class_addMethod([self class], @selector(cobrowseHandleFullDeviceRequest:), (IMP) noopImplementation, "v@:");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
RCT_EXPORT_METHOD(overwriteSessionIndicator) {
|
|
239
|
+
class_addMethod([self class], @selector(cobrowseShowSessionControls:), (IMP) noopImplementation, "v@:");
|
|
240
|
+
class_addMethod([self class], @selector(cobrowseHideSessionControls:), (IMP) noopImplementation, "v@:");
|
|
241
|
+
}
|
|
242
|
+
|
|
283
243
|
@end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Platform, requireNativeComponent } from 'react-native'
|
|
3
|
+
|
|
4
|
+
const CBIOBroadcastPickerView = requireNativeComponent(
|
|
5
|
+
'CBIOBroadcastPickerView'
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
export default function (props) {
|
|
9
|
+
// this native component is only available on iOS
|
|
10
|
+
return Platform.OS === 'ios' ? <CBIOBroadcastPickerView {...props} /> : null
|
|
11
|
+
}
|
package/js/CobrowseIO.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Alert } from 'react-native'
|
|
1
|
+
import { Platform, Alert } from 'react-native'
|
|
2
2
|
import Session from './Session'
|
|
3
3
|
const CobrowseIONative = require('react-native').NativeModules.CobrowseIO
|
|
4
4
|
const NativeEventEmitter = require('react-native').NativeEventEmitter
|
|
@@ -72,18 +72,22 @@ export default class CobrowseIO {
|
|
|
72
72
|
return CobrowseIONative.stop()
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
// eslint-disable-next-line accessor-pairs
|
|
75
76
|
static set api (api) {
|
|
76
77
|
CobrowseIONative.api(api)
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
// eslint-disable-next-line accessor-pairs
|
|
79
81
|
static set license (license) {
|
|
80
82
|
CobrowseIONative.license(license)
|
|
81
83
|
}
|
|
82
84
|
|
|
85
|
+
// eslint-disable-next-line accessor-pairs
|
|
83
86
|
static set customData (customData) {
|
|
84
87
|
CobrowseIONative.customData(customData)
|
|
85
88
|
}
|
|
86
89
|
|
|
90
|
+
// eslint-disable-next-line accessor-pairs
|
|
87
91
|
static set deviceToken (token) {
|
|
88
92
|
CobrowseIONative.deviceToken(token)
|
|
89
93
|
}
|
|
@@ -114,6 +118,14 @@ export default class CobrowseIO {
|
|
|
114
118
|
// the session.requested event is considered internal, it should
|
|
115
119
|
// not be used outside these bindings
|
|
116
120
|
CobrowseIO.addListener('session.requested', (session) => {
|
|
121
|
+
if (CobrowseIO.showSessionControls === false) {
|
|
122
|
+
CobrowseIONative.overwriteSessionIndicator()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (Platform.OS === 'ios' && CobrowseIO.handleFullDeviceRequest) {
|
|
126
|
+
CobrowseIONative.overwriteFullControlUI()
|
|
127
|
+
}
|
|
128
|
+
|
|
117
129
|
CobrowseIO.handleSessionRequest(session)
|
|
118
130
|
})
|
|
119
131
|
|
|
@@ -121,4 +133,13 @@ CobrowseIO.addListener('session.updated', (session) => {
|
|
|
121
133
|
if (session.isActive() && session.remote_control === 'requested') {
|
|
122
134
|
CobrowseIO.handleRemoteControlRequest(session)
|
|
123
135
|
}
|
|
136
|
+
|
|
137
|
+
if (session.isActive() && session.full_device_state === 'requested') {
|
|
138
|
+
if (CobrowseIO.handleFullDeviceRequest) {
|
|
139
|
+
CobrowseIO.handleFullDeviceRequest(session)
|
|
140
|
+
} else if (Platform.OS === 'android') {
|
|
141
|
+
// accept the incoming connection by default
|
|
142
|
+
session.setFullDevice('on')
|
|
143
|
+
}
|
|
144
|
+
}
|
|
124
145
|
})
|
package/js/CobrowseView.js
CHANGED
|
@@ -60,6 +60,7 @@ export default class CobrowseView extends Component {
|
|
|
60
60
|
})
|
|
61
61
|
this.endListener = CobrowseIO.addListener('session.ended', (session) => {
|
|
62
62
|
if (this.props.onEnded) this.props.onEnded()
|
|
63
|
+
this.setState({ session: null })
|
|
63
64
|
})
|
|
64
65
|
}
|
|
65
66
|
|
|
@@ -99,7 +100,7 @@ export default class CobrowseView extends Component {
|
|
|
99
100
|
renderManageSession () {
|
|
100
101
|
return (
|
|
101
102
|
<View>
|
|
102
|
-
<Text style={[styles.text]}>You
|
|
103
|
+
<Text style={[styles.text]}>You're sharing screens from this app with a support agent.</Text>
|
|
103
104
|
<TouchableOpacity onPress={() => this.endSession()}>
|
|
104
105
|
<Text style={[styles.text, styles.button]}>End Session</Text>
|
|
105
106
|
</TouchableOpacity>
|
package/js/Session.js
CHANGED
package/js/Unredacted.js
CHANGED
|
@@ -69,8 +69,8 @@ export const useUnredaction = (shouldWarnUnhandledRefs = true, componentName = '
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
// HOC for adding unredaction to a whole component class
|
|
72
|
-
export function unredact(Component) {
|
|
73
|
-
return React.forwardRef(function Redacted(props, ref) {
|
|
72
|
+
export function unredact (Component) {
|
|
73
|
+
return React.forwardRef(function Redacted (props, ref) {
|
|
74
74
|
const localRef = useUnredaction(true, Component?.name)
|
|
75
75
|
const refs = useMemo(() => mergeRefs([localRef, ref]), [localRef, ref])
|
|
76
76
|
return <Component {...props} collapsable={false} ref={refs} />
|
package/js/index.js
CHANGED
|
@@ -4,3 +4,5 @@ export { default as Redacted } from './Redacted'
|
|
|
4
4
|
export { default as Unredacted, unredact, useUnredaction } from './Unredacted'
|
|
5
5
|
export { default as SessionControl } from './SessionControl'
|
|
6
6
|
export { default as CobrowseAccessibilityService } from './CobrowseAccessibilityService'
|
|
7
|
+
export { default as CBIOBroadcastPickerView } from './CBIOBroadcastPickerView'
|
|
8
|
+
export { useSession } from './useSession'
|