1 // Copyright 2015-present 650 Industries. All rights reserved. 2 3 import ExpoModulesCore 4 import React 5 6 #if DEBUG && EX_DEV_CLIENT_NETWORK_INSPECTOR 7 8 var isHookInstalled = false 9 10 @objc(EXDevLauncherNetworkInterceptor) 11 public final class DevLauncherNetworkInterceptor: NSObject, ExpoRequestCdpInterceptorDelegate { 12 fileprivate static var inspectorPackagerConn: RCTInspectorPackagerConnection? 13 14 public override init() { 15 super.init() 16 assert(Thread.isMainThread) 17 18 if !isHookInstalled { 19 EXDevLauncherUtils.swizzleClassMethod( 20 selector: #selector(RCTInspectorDevServerHelper.connect(withBundleURL:)), 21 withSelector: #selector(RCTInspectorDevServerHelper.EXDevLauncher_connect(withBundleURL:)), 22 forClass: RCTInspectorDevServerHelper.self 23 ) 24 EXDevLauncherUtils.swizzleClassMethod( 25 selector: #selector(getter: URLSessionConfiguration.default), 26 withSelector: #selector(URLSessionConfiguration.EXDevLauncher_urlSessionConfiguration), 27 forClass: URLSessionConfiguration.self 28 ) 29 isHookInstalled = true 30 } 31 32 ExpoRequestCdpInterceptor.shared.setDelegate(self) 33 } 34 35 deinit { 36 assert(Thread.isMainThread) 37 ExpoRequestCdpInterceptor.shared.setDelegate(nil) 38 } 39 40 // MARK: ExpoRequestCdpInterceptorDelegate implementations 41 dispatchnull42 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 EXDevLauncher_connectnull55 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 66 } 67 return inspectorPackagerConn 68 } 69 } 70 71 extension RCTInspectorPackagerConnection { 72 /** 73 Indicates whether the packager connection is established and ready to send messages 74 */ isReadyToSendnull75 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 } 91 92 /** 93 Sends message from native to inspector proxy 94 */ sendWrappedEventToAllPagesnull95 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 } 102 } 103 } 104 105 extension URLSessionConfiguration { 106 private typealias GetterFunc = @convention(c) (AnyObject, Selector) -> URLSessionConfiguration 107 108 /** 109 Swizzled `URLSessionConfiguration.default` for us to add the `EXDevLauncherRequestLoggerProtocol` interceptor 110 */ 111 @objc EXDevLauncher_urlSessionConfigurationnull112 static func EXDevLauncher_urlSessionConfiguration() -> URLSessionConfiguration { 113 guard let config = try? EXDevLauncherUtils.invokeOriginalClassMethod( 114 selector: #selector(getter: URLSessionConfiguration.default), 115 forClass: URLSessionConfiguration.self 116 ) as? URLSessionConfiguration else { 117 fatalError("Unable to get original URLSessionConfiguration.default") 118 } 119 var protocolClasses: [AnyClass] = config.protocolClasses ?? [] 120 if !protocolClasses.contains(where: { $0 == ExpoRequestInterceptorProtocol.self }) { 121 protocolClasses.insert(ExpoRequestInterceptorProtocol.self, at: 0) 122 } 123 config.protocolClasses = protocolClasses 124 return config 125 } 126 } 127 128 #else 129 130 @objc(EXDevLauncherNetworkInterceptor) 131 public final class DevLauncherNetworkInterceptor: NSObject { 132 } 133 134 #endif 135