cobrowse-sdk-react-native 2.13.0 → 2.14.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 +20 -0
- package/.github/workflows/release.yml +23 -0
- package/DEPLOYMENT.md +7 -0
- package/README.md +6 -33
- 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 +196 -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/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 +18 -3
- package/js/CBIOBroadcastPickerView.js +11 -0
- package/js/CobrowseIO.js +18 -1
- package/js/CobrowseView.js +1 -0
- 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 +10 -9
- package/ts/CBIOBroadcastPickerView.d.ts +8 -0
- package/ts/Redacted.d.ts +3 -3
- package/ts/Session.d.ts +3 -1
- package/ts/Unredacted.d.ts +2 -2
- package/ts/index.d.ts +1 -0
- package/ts/useSession.d.ts +3 -0
|
@@ -0,0 +1,196 @@
|
|
|
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.annotation.Nullable;
|
|
11
|
+
import androidx.core.util.Predicate;
|
|
12
|
+
|
|
13
|
+
import com.facebook.react.bridge.Promise;
|
|
14
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
15
|
+
import com.facebook.react.bridge.ReadableArray;
|
|
16
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
17
|
+
import com.facebook.react.uimanager.NativeViewHierarchyManager;
|
|
18
|
+
import com.facebook.react.uimanager.UIBlock;
|
|
19
|
+
import com.facebook.react.uimanager.UIManagerModule;
|
|
20
|
+
|
|
21
|
+
import java.util.ArrayList;
|
|
22
|
+
import java.util.HashSet;
|
|
23
|
+
import java.util.List;
|
|
24
|
+
import java.util.Set;
|
|
25
|
+
|
|
26
|
+
import io.cobrowse.Session;
|
|
27
|
+
|
|
28
|
+
class CommonDelegates implements CobrowseIOCommonDelegates {
|
|
29
|
+
|
|
30
|
+
private static final String SESSION_LOADED = "session.loaded";
|
|
31
|
+
private static final String SESSION_UPDATED = "session.updated";
|
|
32
|
+
private static final String SESSION_ENDED = "session.ended";
|
|
33
|
+
private static final String SESSION_REQUESTED = "session.requested";
|
|
34
|
+
|
|
35
|
+
private final HashSet<Integer> unredactedTags = new HashSet<>();
|
|
36
|
+
private ReactApplicationContext reactApplicationContext;
|
|
37
|
+
private NativeViewHierarchyManager nodeManager;
|
|
38
|
+
|
|
39
|
+
CommonDelegates(ReactApplicationContext context) {
|
|
40
|
+
this.reactApplicationContext = context;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private Set<View> getUnredactedViews(@NonNull final Activity activity) {
|
|
44
|
+
synchronized (unredactedTags) {
|
|
45
|
+
HashSet<View> unredacted = new HashSet<>();
|
|
46
|
+
for (Integer i : unredactedTags) {
|
|
47
|
+
try {
|
|
48
|
+
unredacted.add(nodeManager.resolveView(i));
|
|
49
|
+
} catch (Exception e) {
|
|
50
|
+
Log.i("CobrowseIO", "Failed to find unredacted view for tag " + i + ", error = " + e.getMessage());
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (CobrowseIOModule.delegate instanceof io.cobrowse.reactnative.CobrowseIO.RedactionDelegate) {
|
|
54
|
+
List<View> views = ((io.cobrowse.reactnative.CobrowseIO.RedactionDelegate) CobrowseIOModule.delegate).unredactedViews(activity);
|
|
55
|
+
if (views != null) unredacted.addAll(views);
|
|
56
|
+
}
|
|
57
|
+
return unredacted;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private Set<View> getRedactedViews(@NonNull final Activity activity) {
|
|
62
|
+
HashSet<View> redacted = new HashSet<>(RedactedViewManager.redactedViews.keySet());
|
|
63
|
+
if (CobrowseIOModule.delegate instanceof io.cobrowse.reactnative.CobrowseIO.RedactionDelegate) {
|
|
64
|
+
List<View> views = ((io.cobrowse.reactnative.CobrowseIO.RedactionDelegate) CobrowseIOModule.delegate).redactedViews(activity);
|
|
65
|
+
if (views != null) redacted.addAll(views);
|
|
66
|
+
}
|
|
67
|
+
return redacted;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@Override
|
|
71
|
+
public void handleFullDeviceRequest(@NonNull Activity activity, @NonNull Session session) {
|
|
72
|
+
// no-op, this will be handed on the JS side via an "updated" event handler
|
|
73
|
+
// this stub just disables the default native prompt in the SDK
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@Override
|
|
77
|
+
public List<View> redactedViews(@NonNull Activity activity) {
|
|
78
|
+
final Set<View> redacted = this.getRedactedViews(activity);
|
|
79
|
+
final Set<View> unredacted = this.getUnredactedViews(activity);
|
|
80
|
+
|
|
81
|
+
// Project all unredactions to the root to get the full set of unredacted nodes
|
|
82
|
+
HashSet<View> projectedUnredacted = new HashSet<>();
|
|
83
|
+
for (View view : unredacted) {
|
|
84
|
+
projectedUnredacted.add(view);
|
|
85
|
+
projectedUnredacted.addAll(TreeUtils.allParents(view));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Work out which nodes are explicitly redacted but need to be unredacted
|
|
89
|
+
// due to a nested unredaction tag (these redactions will be moved towards the
|
|
90
|
+
// leaves of the view hierarchy instead)
|
|
91
|
+
HashSet<View> redactedProjectedUnredactions = new HashSet<>(redacted);
|
|
92
|
+
redactedProjectedUnredactions.retainAll(projectedUnredacted);
|
|
93
|
+
|
|
94
|
+
// Start to build the set of redactions we should actually apply. We start off with
|
|
95
|
+
// the set of explicitly redacted nodes
|
|
96
|
+
HashSet<View> toRedact = new HashSet<>(redacted);
|
|
97
|
+
|
|
98
|
+
// remove any redactions that have unredacted decendants (i.e. any that appear in the
|
|
99
|
+
// projection of the unredaction). These are the redacted nodes we need to move towards
|
|
100
|
+
// the leaves of the tree
|
|
101
|
+
toRedact.removeAll(redactedProjectedUnredactions);
|
|
102
|
+
|
|
103
|
+
// Work out the set of all nodes that are siblings of any projected unredacted node
|
|
104
|
+
HashSet<View> projectedUnredactionSiblings = new HashSet<>();
|
|
105
|
+
for (View view : projectedUnredacted) {
|
|
106
|
+
ViewParent parent = view.getParent();
|
|
107
|
+
if (parent instanceof ViewGroup)
|
|
108
|
+
projectedUnredactionSiblings.addAll(TreeUtils.directChildren((ViewGroup) parent));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Subtract the projected unredactions from the set of all siblings of projected
|
|
112
|
+
// unredactions. i.e. subtract things that should be definitely unredacted to leave
|
|
113
|
+
// a set we're not sure yet whether to redact or not
|
|
114
|
+
HashSet<View> potentiallyRedactedUnredactedSiblings = new HashSet<View>(projectedUnredactionSiblings);
|
|
115
|
+
potentiallyRedactedUnredactedSiblings.removeAll(projectedUnredacted);
|
|
116
|
+
|
|
117
|
+
// for each node we're not sure about yet, check if the closest redacted or unredacted ancestor
|
|
118
|
+
// is a redacted node, if so this descendant should also be redacted
|
|
119
|
+
for (View view : potentiallyRedactedUnredactedSiblings) {
|
|
120
|
+
View closest = TreeUtils.closest(view, new Predicate<View>() {
|
|
121
|
+
@Override
|
|
122
|
+
public boolean test(View view) {
|
|
123
|
+
return redacted.contains(view) || unredacted.contains(view);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
if (redacted.contains(closest)) toRedact.add(view);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Remove any empty ViewGroup from the redacted set, they're often used for wrapping
|
|
130
|
+
// or sizing other elements, and do not usually need to be redacted
|
|
131
|
+
// If it's absolutely necessary they are redacted, they can always be replaced with
|
|
132
|
+
// a <Redacted> tag instead
|
|
133
|
+
for (View v : new HashSet<>(toRedact)) {
|
|
134
|
+
if (v instanceof ViewGroup && ((ViewGroup) v).getChildCount() == 0) {
|
|
135
|
+
toRedact.remove(v);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return new ArrayList<>(toRedact);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@Override
|
|
143
|
+
public void handleRemoteControlRequest(@NonNull Activity activity, @NonNull Session session) {
|
|
144
|
+
// no-op, this will be handed on the JS side via an "updated" event handler
|
|
145
|
+
// this stub just disables the default native prompt in the SDK
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@Override
|
|
149
|
+
public void sessionDidLoad(@NonNull Session session) {
|
|
150
|
+
reactApplicationContext
|
|
151
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
152
|
+
.emit(SESSION_LOADED, Conversion.convert(session));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@Override
|
|
156
|
+
public void handleSessionRequest(@NonNull Activity activity, @NonNull Session session) {
|
|
157
|
+
reactApplicationContext
|
|
158
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
159
|
+
.emit(SESSION_REQUESTED, Conversion.convert(session));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@Override
|
|
163
|
+
public void sessionDidUpdate(@NonNull Session session) {
|
|
164
|
+
reactApplicationContext
|
|
165
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
166
|
+
.emit(SESSION_UPDATED, Conversion.convert(session));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@Override
|
|
170
|
+
public void sessionDidEnd(@NonNull Session session) {
|
|
171
|
+
reactApplicationContext
|
|
172
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
173
|
+
.emit(SESSION_ENDED, Conversion.convert(session));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
public void findNodeManager() {
|
|
177
|
+
if (nodeManager != null) return;
|
|
178
|
+
final UIManagerModule uiManager = reactApplicationContext.getNativeModule(UIManagerModule.class);
|
|
179
|
+
assert uiManager != null;
|
|
180
|
+
uiManager.prependUIBlock(new UIBlock() {
|
|
181
|
+
@Override
|
|
182
|
+
public void execute(NativeViewHierarchyManager nativeViewHierarchyManager) {
|
|
183
|
+
nodeManager = nativeViewHierarchyManager;
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
public void setUnredactedTags(final ReadableArray reactTags, final Promise promise) {
|
|
189
|
+
synchronized (unredactedTags) {
|
|
190
|
+
unredactedTags.clear();
|
|
191
|
+
for (int i = 0; i < reactTags.size(); i++)
|
|
192
|
+
unredactedTags.add(reactTags.getInt(i));
|
|
193
|
+
promise.resolve(null);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -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 {
|
|
@@ -115,7 +116,7 @@ RCT_EXPORT_MODULE();
|
|
|
115
116
|
[projectedUnredacted addObject:view];
|
|
116
117
|
[projectedUnredacted addObjectsFromArray: [RCTCBIOTreeUtils allParents: view]];
|
|
117
118
|
}
|
|
118
|
-
|
|
119
|
+
|
|
119
120
|
// Work out which nodes are explicitly redacted but need to be unredacted
|
|
120
121
|
// due to a nested unredaction tag (these redactions will be moved towards the
|
|
121
122
|
// leaves of the view hierarchy instead)
|
|
@@ -158,7 +159,7 @@ RCT_EXPORT_MODULE();
|
|
|
158
159
|
// a <Redacted> tag instead
|
|
159
160
|
for (UIView* v in [toRedact copy])
|
|
160
161
|
if ([v isKindOfClass:RCTView.class] && v.subviews.count == 0) [toRedact removeObject: v];
|
|
161
|
-
|
|
162
|
+
|
|
162
163
|
return toRedact.allObjects;
|
|
163
164
|
}
|
|
164
165
|
|
|
@@ -280,4 +281,18 @@ RCT_REMAP_METHOD(updateSession,
|
|
|
280
281
|
}];
|
|
281
282
|
}
|
|
282
283
|
|
|
284
|
+
void noopImplementation(id self, SEL _cmd, CBIOSession* session) {
|
|
285
|
+
// no-op function which will overwrite the default full device UI when enabled so
|
|
286
|
+
// it can be handled on RN side
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
RCT_EXPORT_METHOD(overwriteFullControlUI) {
|
|
290
|
+
class_addMethod([self class], @selector(cobrowseHandleFullDeviceRequest:), (IMP) noopImplementation, "v@:");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
RCT_EXPORT_METHOD(overwriteSessionIndicator) {
|
|
294
|
+
class_addMethod([self class], @selector(cobrowseShowSessionControls:), (IMP) noopImplementation, "v@:");
|
|
295
|
+
class_addMethod([self class], @selector(cobrowseHideSessionControls:), (IMP) noopImplementation, "v@:");
|
|
296
|
+
}
|
|
297
|
+
|
|
283
298
|
@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
|
|
@@ -114,11 +114,28 @@ export default class CobrowseIO {
|
|
|
114
114
|
// the session.requested event is considered internal, it should
|
|
115
115
|
// not be used outside these bindings
|
|
116
116
|
CobrowseIO.addListener('session.requested', (session) => {
|
|
117
|
+
if (CobrowseIO.showSessionControls === false) {
|
|
118
|
+
CobrowseIONative.overwriteSessionIndicator()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (Platform.OS === 'ios' && CobrowseIO.handleFullDeviceRequest) {
|
|
122
|
+
CobrowseIONative.overwriteFullControlUI()
|
|
123
|
+
}
|
|
124
|
+
|
|
117
125
|
CobrowseIO.handleSessionRequest(session)
|
|
118
126
|
})
|
|
119
127
|
|
|
120
128
|
CobrowseIO.addListener('session.updated', (session) => {
|
|
121
129
|
if (session.isActive() && session.remote_control === 'requested') {
|
|
122
130
|
CobrowseIO.handleRemoteControlRequest(session)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (session.isActive() && session.full_device_state === 'requested') {
|
|
134
|
+
if (CobrowseIO.handleFullDeviceRequest) {
|
|
135
|
+
CobrowseIO.handleFullDeviceRequest(session)
|
|
136
|
+
} else if (Platform.OS === 'android') {
|
|
137
|
+
// accept the incoming connection by default
|
|
138
|
+
session.setFullDevice('on')
|
|
139
|
+
}
|
|
123
140
|
}
|
|
124
141
|
})
|
package/js/CobrowseView.js
CHANGED
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'
|
package/js/useSession.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import CobrowseIO from './CobrowseIO'
|
|
3
|
+
|
|
4
|
+
export function useSession () {
|
|
5
|
+
const [session, setSession] = useState(null)
|
|
6
|
+
|
|
7
|
+
// initial value
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
let isMounted = true
|
|
10
|
+
const getSession = async () => {
|
|
11
|
+
const session = await CobrowseIO.currentSession()
|
|
12
|
+
|
|
13
|
+
if (isMounted) {
|
|
14
|
+
setSession(session)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getSession()
|
|
19
|
+
|
|
20
|
+
return () => {
|
|
21
|
+
isMounted = false
|
|
22
|
+
}
|
|
23
|
+
}, [])
|
|
24
|
+
|
|
25
|
+
// listen to session updates
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const subscription = CobrowseIO.addListener(CobrowseIO.SESSION_UPDATED, setSession)
|
|
28
|
+
|
|
29
|
+
return () => subscription.remove()
|
|
30
|
+
}, [])
|
|
31
|
+
|
|
32
|
+
return session
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cobrowse-sdk-react-native",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.0",
|
|
4
4
|
"description": "Cobrowse SDK for React Native",
|
|
5
5
|
"main": "js/index.js",
|
|
6
6
|
"types": "ts/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"lint
|
|
8
|
+
"lint": "ts-standard ts/*.d.ts",
|
|
9
|
+
"lint:fix": "ts-standard ts/*.d.ts --fix",
|
|
9
10
|
"start:dev": "nodemon --exec ./scripts/copy.sh"
|
|
10
11
|
},
|
|
11
12
|
"author": {
|
|
@@ -27,17 +28,17 @@
|
|
|
27
28
|
],
|
|
28
29
|
"license": "Apache-2.0",
|
|
29
30
|
"optionalDependencies": {
|
|
30
|
-
"@types/react": "^18.0.
|
|
31
|
-
"@types/react-native": "^0.
|
|
31
|
+
"@types/react": "^18.0.24",
|
|
32
|
+
"@types/react-native": "^0.70.6"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
35
|
"lodash": "^4.17.21"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
|
-
"@types/react": "^18.0.
|
|
38
|
-
"@types/react-native": "^0.
|
|
39
|
-
"nodemon": "^2.0.
|
|
40
|
-
"ts-standard": "^
|
|
41
|
-
"typescript": "^4.8.
|
|
38
|
+
"@types/react": "^18.0.24",
|
|
39
|
+
"@types/react-native": "^0.70.6",
|
|
40
|
+
"nodemon": "^2.0.20",
|
|
41
|
+
"ts-standard": "^12.0.1",
|
|
42
|
+
"typescript": "^4.8.4"
|
|
42
43
|
}
|
|
43
44
|
}
|