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.
@@ -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
- static WritableMap convert(Session session) {
13
- WritableMap map = Arguments.createMap();
14
- if (session == null) return null;
15
- map.putString("code", session.code());
16
- map.putString("state", session.state());
17
- map.putString("id", session.id());
18
- map.putBoolean("full_device", session.fullDevice());
19
- map.putString("remote_control", Conversion.remoteControl(session.remoteControl()));
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
- Agent agent = session.agent();
22
- if (agent != null) {
23
- WritableMap agentMap = Arguments.createMap();
24
- agentMap.putString("name", agent.name);
25
- agentMap.putString("id", agent.id);
26
- map.putMap("agent", agentMap);
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
- return map;
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
- static String convert(Error error) {
33
- if (error == null) return null;
34
- return error.getMessage();
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
- static String remoteControl (RemoteControlState state) {
38
- switch (state) {
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
- static RemoteControlState remoteControl (String state) {
53
- switch (state) {
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
- return RemoteControlState.Off;
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
  }
@@ -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,5 @@
1
+ #import <React/RCTViewManager.h>
2
+
3
+ @interface CBIOBroadcastPickerView : RCTViewManager
4
+
5
+ @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
@@ -4,5 +4,6 @@
4
4
  @interface CBIORCTUtil : NSObject
5
5
 
6
6
  +(NSString*) remoteControl: (CBIORemoteControlState) state;
7
+ +(NSString*) fullDeviceState: (CBIOFullDeviceState) state;
7
8
 
8
9
  @end
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,
@@ -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
- return dispatch_get_main_queue();
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
  })
@@ -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
 
package/js/Session.js CHANGED
@@ -61,6 +61,10 @@ export default class Session {
61
61
  return this._session.full_device
62
62
  }
63
63
 
64
+ get full_device_state () {
65
+ return this._session.full_device_state
66
+ }
67
+
64
68
  get remote_control () {
65
69
  return this._session.remote_control
66
70
  }
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'
@@ -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.13.0",
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-fix": "ts-standard ts/*.d.ts --fix",
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.18",
31
- "@types/react-native": "^0.69.6"
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.18",
38
- "@types/react-native": "^0.69.6",
39
- "nodemon": "^2.0.19",
40
- "ts-standard": "^11.0.0",
41
- "typescript": "^4.8.2"
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
  }