expo-dev-launcher 5.0.2 → 5.0.3

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/CHANGELOG.md CHANGED
@@ -10,6 +10,12 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 5.0.3 — 2024-10-24
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - Fixed network inspector support for react-native 0.76. ([#32290](https://github.com/expo/expo/pull/32290) by [@kudo](https://github.com/kudo))
18
+
13
19
  ## 5.0.2 — 2024-10-24
14
20
 
15
21
  ### 🎉 New features
@@ -17,7 +17,7 @@ android {
17
17
  namespace "expo.modules.devlauncher"
18
18
  defaultConfig {
19
19
  versionCode 9
20
- versionName "5.0.2"
20
+ versionName "5.0.3"
21
21
  }
22
22
 
23
23
  buildTypes {
@@ -107,8 +107,7 @@ class DevLauncherController private constructor() :
107
107
 
108
108
  private var appIsLoading = false
109
109
 
110
- @Suppress("unused")
111
- private val networkInterceptor = DevLauncherNetworkInterceptor(this)
110
+ private var networkInterceptor: DevLauncherNetworkInterceptor? = null
112
111
 
113
112
  private fun isEASUpdateURL(url: Uri): Boolean {
114
113
  return url.host.equals("u.expo.dev") || url.host.equals("staging-u.expo.dev")
@@ -192,6 +191,11 @@ class DevLauncherController private constructor() :
192
191
  synchronized(this) {
193
192
  appIsLoading = false
194
193
  }
194
+ manifestURL?.let {
195
+ runBlockingOnMainThread {
196
+ networkInterceptor = DevLauncherNetworkInterceptor(it)
197
+ }
198
+ }
195
199
  }
196
200
 
197
201
  override fun onAppLoadedWithError() {
@@ -287,6 +291,8 @@ class DevLauncherController private constructor() :
287
291
  private fun ensureHostWasCleared(host: ReactHostWrapper, activityToBeInvalidated: ReactActivity? = null) {
288
292
  if (host.hasInstance) {
289
293
  runBlockingOnMainThread {
294
+ networkInterceptor?.close()
295
+ networkInterceptor = null
290
296
  clearHost(host, activityToBeInvalidated)
291
297
  }
292
298
  }
@@ -2,113 +2,42 @@
2
2
 
3
3
  package expo.modules.devlauncher.launcher
4
4
 
5
- import com.facebook.react.bridge.Inspector
6
- import com.facebook.react.common.LifecycleState
7
- import com.facebook.react.devsupport.DevServerHelper
8
- import com.facebook.react.devsupport.DevSupportManagerBase
9
- import com.facebook.react.devsupport.InspectorPackagerConnection
10
- import expo.modules.devlauncher.DevLauncherController
11
- import expo.interfaces.devmenu.ReactHostWrapper
5
+ import android.net.Uri
6
+ import com.facebook.react.packagerconnection.ReconnectingWebSocket
12
7
  import expo.modules.kotlin.devtools.ExpoRequestCdpInterceptor
13
8
  import java.io.Closeable
14
- import java.lang.ref.WeakReference
15
- import java.lang.reflect.Field
16
- import java.lang.reflect.Method
17
9
 
18
- internal class DevLauncherNetworkInterceptor(controller: DevLauncherController) : Closeable, ExpoRequestCdpInterceptor.Delegate {
19
- private val weakController = WeakReference(controller)
20
- private var reactHostHashCode: Int = 0
21
- private var _inspectorPackagerConnection: InspectorPackagerConnectionWrapper? = null
22
-
23
- private val inspectorPackagerConnection: InspectorPackagerConnectionWrapper
24
- get() {
25
- val reactHost = requireNotNull(weakController.get()?.appHost)
26
- if (reactHostHashCode != reactHost.hashCode()) {
27
- _inspectorPackagerConnection?.clear()
28
- _inspectorPackagerConnection = null
29
- reactHostHashCode = 0
30
- }
31
- if (_inspectorPackagerConnection == null) {
32
- _inspectorPackagerConnection = InspectorPackagerConnectionWrapper(reactHost)
33
- reactHostHashCode = reactHost.hashCode()
34
- }
35
- return requireNotNull(_inspectorPackagerConnection)
10
+ internal class DevLauncherNetworkInterceptor(appUrl: Uri) : Closeable, ExpoRequestCdpInterceptor.Delegate {
11
+ private val metroConnection = ReconnectingWebSocket(
12
+ createNetworkInspectorUrl(appUrl),
13
+ null,
14
+ null
15
+ )
16
+ .apply {
17
+ connect()
36
18
  }
37
19
 
38
20
  init {
39
21
  ExpoRequestCdpInterceptor.setDelegate(this)
40
22
  }
41
23
 
42
- /**
43
- * Returns true when it is allowed to send CDP events
44
- */
45
- private fun shouldEmitEvents(): Boolean {
46
- return DevLauncherController.wasInitialized() && weakController.get()?.appHost?.lifecycleState == LifecycleState.RESUMED
47
- }
48
-
49
24
  //region Closeable implementations
50
25
  override fun close() {
51
26
  ExpoRequestCdpInterceptor.setDelegate(null)
27
+ metroConnection.closeQuietly()
52
28
  }
53
29
  //endregion Closeable implementations
54
30
 
55
31
  //region ExpoRequestCdpInterceptor.Delegate implementations
56
32
  override fun dispatch(event: String) {
57
- if (shouldEmitEvents()) {
58
- inspectorPackagerConnection.sendWrappedEventToAllPages(event)
59
- }
33
+ metroConnection.sendMessage(event)
60
34
  }
61
35
  //endregion ExpoRequestCdpInterceptor.Delegate implementations
62
36
  }
63
37
 
64
- /**
65
- * A `InspectorPackagerConnection` wrapper to expose private members with reflection
66
- */
67
- internal class InspectorPackagerConnectionWrapper constructor(reactHost: ReactHostWrapper) {
68
- private var inspectorPackagerConnectionWeak: WeakReference<InspectorPackagerConnection> = WeakReference(null)
69
- private val devServerHelperWeak: WeakReference<DevServerHelper>
70
- private val inspectorPackagerConnectionField: Field
71
- private val sendWrappedEventMethod: Method
72
-
73
- private val inspectorPackagerConnection: InspectorPackagerConnection?
74
- get() {
75
- var inspectorPackagerConnection = inspectorPackagerConnectionWeak.get()
76
- if (inspectorPackagerConnection == null) {
77
- val devServerHelper = devServerHelperWeak.get() ?: return null
78
- inspectorPackagerConnection = inspectorPackagerConnectionField[devServerHelper] as? InspectorPackagerConnection
79
-
80
- if (inspectorPackagerConnection != null) {
81
- inspectorPackagerConnectionWeak = WeakReference(inspectorPackagerConnection)
82
- }
83
- }
84
- return inspectorPackagerConnection
85
- }
86
-
87
- init {
88
- val devSupportManager = requireNotNull(reactHost.devSupportManager)
89
- val devSupportManagerBaseClass = DevSupportManagerBase::class.java
90
- val mDevServerHelperField = devSupportManagerBaseClass.getDeclaredField("mDevServerHelper")
91
- mDevServerHelperField.isAccessible = true
92
- val devServerHelper = mDevServerHelperField[devSupportManager]
93
- devServerHelperWeak = WeakReference(devServerHelper as DevServerHelper)
94
-
95
- inspectorPackagerConnectionField = DevServerHelper::class.java.getDeclaredField("mInspectorPackagerConnection")
96
- inspectorPackagerConnectionField.isAccessible = true
97
-
98
- sendWrappedEventMethod = InspectorPackagerConnection::class.java.getDeclaredMethod("sendWrappedEvent", String::class.java, String::class.java)
99
- sendWrappedEventMethod.isAccessible = true
100
- }
101
-
102
- fun clear() {
103
- inspectorPackagerConnectionWeak.clear()
104
- }
105
-
106
- fun sendWrappedEventToAllPages(event: String) {
107
- val inspectorPackagerConnection = this.inspectorPackagerConnection ?: return
108
- for (page in Inspector.getPages()) {
109
- if (!page.title.contains("Reanimated")) {
110
- sendWrappedEventMethod.invoke(inspectorPackagerConnection, page.id.toString(), event)
111
- }
112
- }
113
- }
38
+ private fun createNetworkInspectorUrl(appUrl: Uri): String {
39
+ val host = appUrl.host ?: "localhost"
40
+ val port = if (appUrl.port > 0) appUrl.port else 8081
41
+ val scheme = if (appUrl.scheme == "https") "wss" else "ws"
42
+ return "$scheme://$host:$port/inspector/network"
114
43
  }
@@ -9,18 +9,18 @@ var isHookInstalled = false
9
9
 
10
10
  @objc(EXDevLauncherNetworkInterceptor)
11
11
  public final class DevLauncherNetworkInterceptor: NSObject, ExpoRequestCdpInterceptorDelegate {
12
- fileprivate static var inspectorPackagerConn: RCTInspectorPackagerConnection?
12
+ private let metroConnection: RCTReconnectingWebSocket
13
13
 
14
- public override init() {
15
- super.init()
14
+ @objc
15
+ public init(bundleUrl: URL) {
16
16
  assert(Thread.isMainThread)
17
+ self.metroConnection = RCTReconnectingWebSocket(
18
+ url: Self.createNetworkInspectorUrl(bundleUrl: bundleUrl),
19
+ queue: .main)
20
+ self.metroConnection.start()
21
+ super.init()
17
22
 
18
23
  if !isHookInstalled {
19
- EXDevLauncherUtils.swizzleClassMethod(
20
- selector: #selector(RCTInspectorDevServerHelper.connect(withBundleURL:)),
21
- withSelector: #selector(RCTInspectorDevServerHelper.EXDevLauncher_connect(withBundleURL:)),
22
- forClass: RCTInspectorDevServerHelper.self
23
- )
24
24
  EXDevLauncherUtils.swizzleClassMethod(
25
25
  selector: #selector(getter: URLSessionConfiguration.default),
26
26
  withSelector: #selector(URLSessionConfiguration.EXDevLauncher_urlSessionConfiguration),
@@ -35,70 +35,24 @@ public final class DevLauncherNetworkInterceptor: NSObject, ExpoRequestCdpInterc
35
35
  deinit {
36
36
  assert(Thread.isMainThread)
37
37
  ExpoRequestCdpInterceptor.shared.setDelegate(nil)
38
+ self.metroConnection.stop()
38
39
  }
39
40
 
40
- // MARK: ExpoRequestCdpInterceptorDelegate implementations
41
-
42
- public func dispatch(_ event: String) {
43
- Self.inspectorPackagerConn?.sendWrappedEventToAllPages(event)
44
- }
45
- }
46
-
47
- extension RCTInspectorDevServerHelper {
48
- private typealias ConnectFunc = @convention(c) (AnyObject, Selector, URL)
49
- -> RCTInspectorPackagerConnection?
50
-
51
- /**
52
- Swizzled `RCTInspectorDevServerHelper.connect(withBundleURL:)` for us to get the `RCTInspectorPackagerConnection` instance
53
- */
54
- @objc
55
- static func EXDevLauncher_connect(withBundleURL bundleURL: URL)
56
- -> RCTInspectorPackagerConnection? {
57
- let inspectorPackagerConn = try? EXDevLauncherUtils.invokeOriginalClassMethod(
58
- selector: #selector(RCTInspectorDevServerHelper.connect(withBundleURL:)),
59
- forClass: RCTInspectorDevServerHelper.self,
60
- A0: bundleURL
61
- ) as? RCTInspectorPackagerConnection
62
-
63
- // Exclude the connections for dev-client bundles
64
- if !bundleURL.absoluteString.starts(with: Bundle.main.bundleURL.absoluteString) {
65
- DevLauncherNetworkInterceptor.inspectorPackagerConn = inspectorPackagerConn
41
+ private static func createNetworkInspectorUrl(bundleUrl: URL) -> URL {
42
+ let host = bundleUrl.host ?? "localhost"
43
+ let port = bundleUrl.port ?? 8081
44
+ let scheme = bundleUrl.scheme == "https" ? "wss" : "ws"
45
+ let urlString = "\(scheme)://\(host):\(port)/inspector/network"
46
+ guard let url = URL(string: urlString) else {
47
+ fatalError("Invalid network inspector URL: \(urlString)")
66
48
  }
67
- return inspectorPackagerConn
49
+ return url
68
50
  }
69
- }
70
51
 
71
- extension RCTInspectorPackagerConnection {
72
- /**
73
- Indicates whether the packager connection is established and ready to send messages
74
- */
75
- func isReadyToSend() -> Bool {
76
- guard isConnected() else {
77
- return false
78
- }
79
- guard let webSocket = value(forKey: "_webSocket") as? AnyObject,
80
- let readyState = webSocket.value(forKey: "readyState") as? Int else {
81
- return false
82
- }
83
- // To support both RCTSRWebSocket (RN < 0.72) and SRWebSocket (RN >= 0.72)
84
- // and not to introduce extra podspec dependencies,
85
- // we use the internal and hardcoded value here.
86
- // Given the fact that both RCTSRWebSocket and SRWebSocket has the readyState property
87
- // and the open state is 1.
88
- let OPEN_STATE = 1
89
- return readyState == OPEN_STATE
90
- }
52
+ // MARK: ExpoRequestCdpInterceptorDelegate implementations
91
53
 
92
- /**
93
- Sends message from native to inspector proxy
94
- */
95
- func sendWrappedEventToAllPages(_ event: String) {
96
- guard isReadyToSend() else {
97
- return
98
- }
99
- for page in RCTInspector.pages() where !page.title.contains("Reanimated") {
100
- perform(NSSelectorFromString("sendWrappedEvent:message:"), with: String(page.id), with: event)
101
- }
54
+ public func dispatch(_ event: String) {
55
+ self.metroConnection.send(Data(event.utf8))
102
56
  }
103
57
  }
104
58
 
@@ -56,7 +56,7 @@
56
56
  @property (nonatomic, strong) NSURL *possibleManifestURL;
57
57
  @property (nonatomic, strong) EXDevLauncherErrorManager *errorManager;
58
58
  @property (nonatomic, strong) EXDevLauncherInstallationIDHelper *installationIDHelper;
59
- @property (nonatomic, strong) EXDevLauncherNetworkInterceptor *networkInterceptor;
59
+ @property (nonatomic, strong, nullable) EXDevLauncherNetworkInterceptor *networkInterceptor;
60
60
  @property (nonatomic, assign) BOOL isStarted;
61
61
  @property (nonatomic, strong) EXDevLauncherAppDelegate *appDelegate;
62
62
  @property (nonatomic, strong) NSURL *lastOpenedAppUrl;
@@ -84,7 +84,6 @@
84
84
  self.pendingDeepLinkRegistry = [EXDevLauncherPendingDeepLinkRegistry new];
85
85
  self.errorManager = [[EXDevLauncherErrorManager alloc] initWithController:self];
86
86
  self.installationIDHelper = [EXDevLauncherInstallationIDHelper new];
87
- self.networkInterceptor = [EXDevLauncherNetworkInterceptor new];
88
87
  self.shouldPreferUpdatesInterfaceSourceUrl = NO;
89
88
 
90
89
  __weak __typeof(self) weakSelf = self;
@@ -323,6 +322,7 @@
323
322
 
324
323
  self.manifest = nil;
325
324
  self.manifestURL = nil;
325
+ self.networkInterceptor = nil;
326
326
 
327
327
  [self _applyUserInterfaceStyle:UIUserInterfaceStyleUnspecified];
328
328
 
@@ -576,6 +576,7 @@
576
576
  if (![bundleUrl.scheme isEqualToString:@"file"]) {
577
577
  [[RCTPackagerConnection sharedPackagerConnection] setSocketConnectionURL:bundleUrl];
578
578
  }
579
+ self.networkInterceptor = [[EXDevLauncherNetworkInterceptor alloc] initWithBundleUrl:bundleUrl];
579
580
  #endif
580
581
 
581
582
  UIUserInterfaceStyle userInterfaceStyle = [EXDevLauncherManifestHelper exportManifestUserInterfaceStyle:manifest.userInterfaceStyle];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "expo-dev-launcher",
3
3
  "title": "Expo Development Launcher",
4
- "version": "5.0.2",
4
+ "version": "5.0.3",
5
5
  "description": "Pre-release version of the Expo development launcher package for testing.",
6
6
  "main": "build/DevLauncher.js",
7
7
  "types": "build/DevLauncher.d.ts",
@@ -66,5 +66,5 @@
66
66
  ],
67
67
  "testTimeout": 15000
68
68
  },
69
- "gitHead": "0a07b965c4bef67e7718355a0dc770d524ad3cee"
69
+ "gitHead": "2e4f18d41da033c5ced0a4a045d91cf5250016b7"
70
70
  }