expo-modules-core 1.3.2 → 1.4.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/CHANGELOG.md +23 -0
- package/ExpoModulesCore.podspec +26 -5
- package/android/ExpoModulesCorePlugin.gradle +4 -0
- package/android/build.gradle +13 -14
- package/android/src/main/AndroidManifest.xml +1 -2
- package/android/src/main/cpp/ExpoModulesHostObject.cpp +3 -0
- package/android/src/main/cpp/JNIDeallocator.cpp +17 -0
- package/android/src/main/cpp/JNIDeallocator.h +25 -0
- package/android/src/main/cpp/JSIInteropModuleRegistry.cpp +8 -1
- package/android/src/main/cpp/JSIInteropModuleRegistry.h +6 -1
- package/android/src/main/cpp/JavaCallback.cpp +9 -0
- package/android/src/main/cpp/JavaCallback.h +12 -2
- package/android/src/main/cpp/JavaScriptFunction.cpp +13 -0
- package/android/src/main/cpp/JavaScriptFunction.h +7 -1
- package/android/src/main/cpp/JavaScriptModuleObject.cpp +2 -1
- package/android/src/main/cpp/JavaScriptObject.cpp +17 -2
- package/android/src/main/cpp/JavaScriptObject.h +10 -3
- package/android/src/main/cpp/JavaScriptRuntime.cpp +5 -4
- package/android/src/main/cpp/JavaScriptRuntime.h +5 -3
- package/android/src/main/cpp/JavaScriptTypedArray.cpp +14 -0
- package/android/src/main/cpp/JavaScriptTypedArray.h +6 -0
- package/android/src/main/cpp/JavaScriptValue.cpp +32 -4
- package/android/src/main/cpp/JavaScriptValue.h +10 -3
- package/android/src/main/cpp/MethodMetadata.cpp +1 -1
- package/android/src/main/cpp/types/FrontendConverter.cpp +8 -4
- package/android/src/main/java/expo/modules/core/interfaces/ReactActivityHandler.java +14 -0
- package/android/src/main/java/expo/modules/kotlin/AppContext.kt +4 -1
- package/android/src/main/java/expo/modules/kotlin/ModuleHolder.kt +7 -3
- package/android/src/main/java/expo/modules/kotlin/ModuleRegistry.kt +19 -12
- package/android/src/main/java/expo/modules/kotlin/devtools/ExpoNetworkInspectOkHttpInterceptors.kt +70 -0
- package/android/src/main/java/expo/modules/kotlin/devtools/ExpoRequestCdpInterceptor.kt +72 -0
- package/android/src/main/java/expo/modules/kotlin/devtools/OkHttpHeadersExtension.kt +18 -0
- package/android/src/main/java/expo/modules/kotlin/devtools/cdp/CdpNetworkTypes.kt +257 -0
- package/android/src/main/java/expo/modules/kotlin/jni/JNIDeallocator.kt +24 -15
- package/android/src/main/java/expo/modules/kotlin/jni/JSIInteropModuleRegistry.kt +8 -1
- package/android/src/main/java/expo/modules/kotlin/jni/JavaCallback.kt +0 -4
- package/android/src/main/java/expo/modules/kotlin/jni/JavaScriptFunction.kt +0 -4
- package/android/src/main/java/expo/modules/kotlin/jni/JavaScriptModuleObject.kt +5 -2
- package/android/src/main/java/expo/modules/kotlin/jni/JavaScriptObject.kt +0 -5
- package/android/src/main/java/expo/modules/kotlin/jni/JavaScriptValue.kt +0 -4
- package/android/src/main/java/expo/modules/kotlin/objects/ObjectDefinitionBuilder.kt +1 -1
- package/android/src/main/java/expo/modules/kotlin/types/ColorTypeConverter.kt +3 -0
- package/android/src/main/java/expo/modules/kotlin/types/TypeConverterProvider.kt +1 -2
- package/android-annotation/build.gradle +2 -2
- package/android-annotation-processor/build.gradle +2 -2
- package/ios/JSI/EXJSIInstaller.h +2 -2
- package/ios/JSI/EXJSIInstaller.mm +6 -6
- package/ios/JSI/EXJavaScriptRuntime.h +0 -6
- package/ios/JSI/EXJavaScriptRuntime.mm +0 -23
- package/ios/RCTComponentData+Privates.h +17 -0
- package/ios/RCTComponentData+Privates.m +15 -0
- package/ios/Swift/AppContext.swift +20 -11
- package/ios/Swift/DevTools/CdpNetworkTypes.swift +163 -0
- package/ios/Swift/DevTools/ExpoRequestCdpInterceptor.swift +71 -0
- package/ios/Swift/DevTools/ExpoRequestInterceptorProtocol.swift +183 -0
- package/ios/Swift/DevTools/URLRequest+httpBodyData.swift +43 -0
- package/ios/Swift/ExpoRuntime.swift +28 -0
- package/ios/Swift/Modules/CoreModule.swift +7 -0
- package/ios/Swift/SharedObjects/SharedRef.swift +12 -0
- package/ios/Swift/Views/ComponentData.swift +1 -1
- package/ios/Tests/CoreModuleSpec.swift +27 -0
- package/ios/Tests/ExpoRequestCdpInterceptorSpec.swift +165 -0
- package/ios/Tests/SharedRefSpec.swift +60 -0
- package/package.json +2 -2
|
@@ -8,7 +8,7 @@ public final class AppContext: NSObject {
|
|
|
8
8
|
internal static func create() -> AppContext {
|
|
9
9
|
let appContext = AppContext()
|
|
10
10
|
|
|
11
|
-
appContext._runtime =
|
|
11
|
+
appContext._runtime = ExpoRuntime()
|
|
12
12
|
return appContext
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -44,7 +44,7 @@ public final class AppContext: NSObject {
|
|
|
44
44
|
Underlying JSI runtime of the running app.
|
|
45
45
|
*/
|
|
46
46
|
@objc
|
|
47
|
-
public var _runtime:
|
|
47
|
+
public var _runtime: ExpoRuntime? {
|
|
48
48
|
didSet {
|
|
49
49
|
if _runtime == nil {
|
|
50
50
|
// When the runtime is unpinned from the context (e.g. deallocated),
|
|
@@ -52,9 +52,8 @@ public final class AppContext: NSObject {
|
|
|
52
52
|
// Otherwise the JSCRuntime asserts may fail on deallocation.
|
|
53
53
|
releaseRuntimeObjects()
|
|
54
54
|
} else if _runtime != oldValue {
|
|
55
|
-
// Try to install
|
|
56
|
-
|
|
57
|
-
try? installExpoModulesHostObject()
|
|
55
|
+
// Try to install the core object automatically when the runtime changes.
|
|
56
|
+
try? prepareRuntime()
|
|
58
57
|
}
|
|
59
58
|
}
|
|
60
59
|
}
|
|
@@ -62,7 +61,7 @@ public final class AppContext: NSObject {
|
|
|
62
61
|
/**
|
|
63
62
|
JSI runtime of the running app.
|
|
64
63
|
*/
|
|
65
|
-
public var runtime:
|
|
64
|
+
public var runtime: ExpoRuntime {
|
|
66
65
|
get throws {
|
|
67
66
|
if let runtime = _runtime {
|
|
68
67
|
return runtime
|
|
@@ -71,6 +70,11 @@ public final class AppContext: NSObject {
|
|
|
71
70
|
}
|
|
72
71
|
}
|
|
73
72
|
|
|
73
|
+
/**
|
|
74
|
+
The core module that defines the `expo` object in the global scope of Expo runtime.
|
|
75
|
+
*/
|
|
76
|
+
internal private(set) lazy var coreModule = CoreModule(appContext: self)
|
|
77
|
+
|
|
74
78
|
/**
|
|
75
79
|
Designated initializer without modules provider.
|
|
76
80
|
*/
|
|
@@ -127,7 +131,8 @@ public final class AppContext: NSObject {
|
|
|
127
131
|
*/
|
|
128
132
|
internal func newObject(nativeClassId: ObjectIdentifier) throws -> JavaScriptObject? {
|
|
129
133
|
guard let jsClass = classRegistry.getJavaScriptClass(nativeClassId: nativeClassId) else {
|
|
130
|
-
|
|
134
|
+
// TODO: Define a JS class for SharedRef in the CoreModule and then use it here instead of a raw object (?)
|
|
135
|
+
return try runtime.createObject()
|
|
131
136
|
}
|
|
132
137
|
let prototype = try jsClass.getProperty("prototype").asObject()
|
|
133
138
|
let object = try runtime.createObject(withPrototype: prototype)
|
|
@@ -342,10 +347,14 @@ public final class AppContext: NSObject {
|
|
|
342
347
|
|
|
343
348
|
// MARK: - Runtime
|
|
344
349
|
|
|
345
|
-
internal func
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
350
|
+
internal func prepareRuntime() throws {
|
|
351
|
+
let runtime = try runtime
|
|
352
|
+
let coreObject = try coreModule.definition().build(appContext: self)
|
|
353
|
+
|
|
354
|
+
// Initialize `global.expo`.
|
|
355
|
+
try runtime.initializeCoreObject(coreObject)
|
|
356
|
+
|
|
357
|
+
// Install the modules host object as the `global.expo.modules`.
|
|
349
358
|
EXJavaScriptRuntimeManager.installExpoModulesHostObject(self)
|
|
350
359
|
}
|
|
351
360
|
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// Copyright 2015-present 650 Industries. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import Foundation
|
|
4
|
+
|
|
5
|
+
struct CdpNetwork {
|
|
6
|
+
// MARK: Types
|
|
7
|
+
|
|
8
|
+
typealias Headers = [String: String]?
|
|
9
|
+
typealias MonotonicTime = TimeInterval
|
|
10
|
+
typealias RequestId = String
|
|
11
|
+
typealias TimeSinceEpoch = TimeInterval
|
|
12
|
+
|
|
13
|
+
enum ResourceType: String, Encodable {
|
|
14
|
+
case image = "Image"
|
|
15
|
+
case media = "Media"
|
|
16
|
+
case font = "Font"
|
|
17
|
+
case script = "Script"
|
|
18
|
+
case fetch = "Fetch"
|
|
19
|
+
case other = "Other"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
struct ConnectTiming: Encodable {
|
|
23
|
+
let requestTime: MonotonicTime
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
struct Request: Encodable {
|
|
27
|
+
let url: String
|
|
28
|
+
let method: String
|
|
29
|
+
let headers: Headers
|
|
30
|
+
let postData: String?
|
|
31
|
+
|
|
32
|
+
init(_ request: URLRequest) {
|
|
33
|
+
self.url = request.url?.absoluteString ?? ""
|
|
34
|
+
self.method = request.httpMethod ?? "GET"
|
|
35
|
+
self.headers = request.allHTTPHeaderFields ?? [:]
|
|
36
|
+
if let httpBody = request.httpBodyData() {
|
|
37
|
+
self.postData = String(data: httpBody, encoding: .utf8)
|
|
38
|
+
} else {
|
|
39
|
+
self.postData = nil
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
struct Response: Encodable {
|
|
45
|
+
let url: String
|
|
46
|
+
let status: Int
|
|
47
|
+
let statusText: String
|
|
48
|
+
let headers: Headers
|
|
49
|
+
let mimeType: String
|
|
50
|
+
let encodedDataLength: Int64
|
|
51
|
+
|
|
52
|
+
init(_ response: HTTPURLResponse) {
|
|
53
|
+
self.url = response.url?.absoluteString ?? ""
|
|
54
|
+
self.status = response.statusCode
|
|
55
|
+
self.statusText = ""
|
|
56
|
+
let headers = response.allHeaderFields.reduce(into: [String: String]()) { result, header in
|
|
57
|
+
if let key = header.key as? String, let value = header.value as? String {
|
|
58
|
+
result[key] = value
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
self.headers = headers
|
|
62
|
+
self.mimeType = response.value(forHTTPHeaderField: "Content-Type") ?? ""
|
|
63
|
+
self.encodedDataLength = response.expectedContentLength
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// MARK: Events
|
|
68
|
+
|
|
69
|
+
struct RequestWillBeSentParams: EventParms {
|
|
70
|
+
let requestId: RequestId
|
|
71
|
+
var loaderId = ""
|
|
72
|
+
var documentURL = "mobile"
|
|
73
|
+
let request: Request
|
|
74
|
+
let timestamp: MonotonicTime
|
|
75
|
+
let wallTime: TimeSinceEpoch
|
|
76
|
+
var initiator = ["type": "script"]
|
|
77
|
+
var redirectHasExtraInfo: Bool {
|
|
78
|
+
return self.redirectResponse != nil
|
|
79
|
+
}
|
|
80
|
+
let redirectResponse: Response?
|
|
81
|
+
var referrerPolicy = "no-referrer"
|
|
82
|
+
let type: ResourceType
|
|
83
|
+
|
|
84
|
+
init(now: TimeInterval, requestId: RequestId, request: URLRequest, redirectResponse: HTTPURLResponse?) {
|
|
85
|
+
self.requestId = requestId
|
|
86
|
+
self.request = Request(request)
|
|
87
|
+
self.timestamp = now
|
|
88
|
+
self.wallTime = now
|
|
89
|
+
if let redirectResponse = redirectResponse {
|
|
90
|
+
self.redirectResponse = Response(redirectResponse)
|
|
91
|
+
} else {
|
|
92
|
+
self.redirectResponse = nil
|
|
93
|
+
}
|
|
94
|
+
self.type = ResourceType.fetch
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
struct RequestWillBeSentExtraInfoParams: EventParms {
|
|
99
|
+
let requestId: RequestId
|
|
100
|
+
var associatedCookies = [String: String]()
|
|
101
|
+
let headers: Headers
|
|
102
|
+
let connectTiming: ConnectTiming
|
|
103
|
+
|
|
104
|
+
init(now: TimeInterval, requestId: RequestId, request: URLRequest) {
|
|
105
|
+
self.requestId = requestId
|
|
106
|
+
self.headers = request.allHTTPHeaderFields ?? [:]
|
|
107
|
+
self.connectTiming = ConnectTiming(requestTime: now)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
struct ResponseReceivedParams: EventParms {
|
|
112
|
+
let requestId: RequestId
|
|
113
|
+
var loaderId = ""
|
|
114
|
+
let timestamp: MonotonicTime
|
|
115
|
+
let type: ResourceType
|
|
116
|
+
let response: Response
|
|
117
|
+
var hasExtraInfo = false
|
|
118
|
+
|
|
119
|
+
init(now: TimeInterval, requestId: RequestId, request: URLRequest, response: HTTPURLResponse) {
|
|
120
|
+
self.requestId = requestId
|
|
121
|
+
self.timestamp = now
|
|
122
|
+
self.type = ResourceType.fetch
|
|
123
|
+
self.response = Response(response)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
struct LoadingFinishedParams: EventParms {
|
|
128
|
+
let requestId: RequestId
|
|
129
|
+
let timestamp: MonotonicTime
|
|
130
|
+
let encodedDataLength: Int64
|
|
131
|
+
|
|
132
|
+
init(now: TimeInterval, requestId: RequestId, request: URLRequest, response: HTTPURLResponse) {
|
|
133
|
+
self.requestId = requestId
|
|
134
|
+
self.timestamp = now
|
|
135
|
+
self.encodedDataLength = response.expectedContentLength
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
struct ExpoReceivedResponseBodyParams: EventParms {
|
|
140
|
+
let requestId: RequestId
|
|
141
|
+
let body: String
|
|
142
|
+
let base64Encoded: Bool
|
|
143
|
+
|
|
144
|
+
init(now: TimeInterval, requestId: RequestId, responseBody: Data, isText: Bool) {
|
|
145
|
+
self.requestId = requestId
|
|
146
|
+
let bodyString = isText ? String(data: responseBody, encoding: .utf8) : responseBody.base64EncodedString()
|
|
147
|
+
if let bodyString = bodyString {
|
|
148
|
+
self.body = bodyString
|
|
149
|
+
self.base64Encoded = !isText
|
|
150
|
+
} else {
|
|
151
|
+
self.body = ""
|
|
152
|
+
self.base64Encoded = false
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
typealias EventParms = Encodable
|
|
158
|
+
|
|
159
|
+
struct Event<T: EventParms>: Encodable {
|
|
160
|
+
let method: String
|
|
161
|
+
let params: T
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Copyright 2015-present 650 Industries. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import Foundation
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
The `ExpoRequestInterceptorProtocolDelegate` implementation to
|
|
7
|
+
dispatch CDP (Chrome DevTools Protocol: https://chromedevtools.github.io/devtools-protocol/) events.
|
|
8
|
+
*/
|
|
9
|
+
@objc(EXRequestCdpInterceptor)
|
|
10
|
+
public final class ExpoRequestCdpInterceptor: NSObject, ExpoRequestInterceptorProtocolDelegate {
|
|
11
|
+
private var delegate: ExpoRequestCdpInterceptorDelegate?
|
|
12
|
+
internal var dispatchQueue = DispatchQueue(label: "expo.requestCdpInterceptor")
|
|
13
|
+
|
|
14
|
+
override private init() {}
|
|
15
|
+
|
|
16
|
+
@objc
|
|
17
|
+
public static let shared = ExpoRequestCdpInterceptor()
|
|
18
|
+
|
|
19
|
+
@objc
|
|
20
|
+
public func setDelegate(_ newValue: ExpoRequestCdpInterceptorDelegate?) {
|
|
21
|
+
dispatchQueue.async {
|
|
22
|
+
self.delegate = newValue
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private func dispatchEvent<T: CdpNetwork.EventParms>(_ event: CdpNetwork.Event<T>) {
|
|
27
|
+
dispatchQueue.async {
|
|
28
|
+
let encoder = JSONEncoder()
|
|
29
|
+
if let jsonData = try? encoder.encode(event), let payload = String(data: jsonData, encoding: .utf8) {
|
|
30
|
+
self.delegate?.dispatch(payload)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// MARK: ExpoRequestInterceptorProtocolDelegate implementations
|
|
36
|
+
|
|
37
|
+
func willSendRequest(requestId: String, request: URLRequest, redirectResponse: HTTPURLResponse?) {
|
|
38
|
+
let now = Date().timeIntervalSince1970
|
|
39
|
+
|
|
40
|
+
let params = CdpNetwork.RequestWillBeSentParams(now: now, requestId: requestId, request: request, redirectResponse: redirectResponse)
|
|
41
|
+
dispatchEvent(CdpNetwork.Event(method: "Network.requestWillBeSent", params: params))
|
|
42
|
+
|
|
43
|
+
let params2 = CdpNetwork.RequestWillBeSentExtraInfoParams(now: now, requestId: requestId, request: request)
|
|
44
|
+
dispatchEvent(CdpNetwork.Event(method: "Network.requestWillBeSentExtraInfo", params: params2))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
func didReceiveResponse(requestId: String, request: URLRequest, response: HTTPURLResponse) {
|
|
48
|
+
let now = Date().timeIntervalSince1970
|
|
49
|
+
|
|
50
|
+
let params = CdpNetwork.ResponseReceivedParams(now: now, requestId: requestId, request: request, response: response)
|
|
51
|
+
dispatchEvent(CdpNetwork.Event(method: "Network.responseReceived", params: params))
|
|
52
|
+
|
|
53
|
+
let params2 = CdpNetwork.LoadingFinishedParams(now: now, requestId: requestId, request: request, response: response)
|
|
54
|
+
dispatchEvent(CdpNetwork.Event(method: "Network.loadingFinished", params: params2))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func didReceiveResponseBody(requestId: String, responseBody: Data, isText: Bool) {
|
|
58
|
+
let now = Date().timeIntervalSince1970
|
|
59
|
+
let params = CdpNetwork.ExpoReceivedResponseBodyParams(now: now, requestId: requestId, responseBody: responseBody, isText: isText)
|
|
60
|
+
dispatchEvent(CdpNetwork.Event(method: "Expo(Network.receivedResponseBody)", params: params))
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
The delegate to dispatch CDP events for ExpoRequestCdpInterceptor
|
|
66
|
+
*/
|
|
67
|
+
@objc(EXRequestCdpInterceptorDelegate)
|
|
68
|
+
public protocol ExpoRequestCdpInterceptorDelegate {
|
|
69
|
+
@objc
|
|
70
|
+
func dispatch(_ event: String)
|
|
71
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// Copyright 2015-present 650 Industries. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import Foundation
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
A `URLSession` interceptor which passes network events to its delegate
|
|
7
|
+
*/
|
|
8
|
+
@objc(EXRequestInterceptorProtocol)
|
|
9
|
+
public final class ExpoRequestInterceptorProtocol: URLProtocol, URLSessionDataDelegate {
|
|
10
|
+
private static let REQUEST_ID = "ExpoRequestInterceptorProtocol.requestId"
|
|
11
|
+
private static var requestIdProvider = RequestIdProvider()
|
|
12
|
+
private lazy var urlSession = URLSession(
|
|
13
|
+
configuration: URLSessionConfiguration.default,
|
|
14
|
+
delegate: self,
|
|
15
|
+
delegateQueue: nil
|
|
16
|
+
)
|
|
17
|
+
private var dataTask_: URLSessionDataTask?
|
|
18
|
+
private let responseBody = NSMutableData()
|
|
19
|
+
private var responseIsText = false
|
|
20
|
+
private var responseContentLength: Int64 = 0
|
|
21
|
+
|
|
22
|
+
static let MAX_BODY_SIZE = 1_048_576
|
|
23
|
+
|
|
24
|
+
// Currently keeps the delegate fixed for ExpoRequestCdpInterceptor and be thread-safe
|
|
25
|
+
static let delegate: ExpoRequestInterceptorProtocolDelegate = ExpoRequestCdpInterceptor.shared
|
|
26
|
+
|
|
27
|
+
// MARK: URLProtocol implementations
|
|
28
|
+
|
|
29
|
+
public override class func canInit(with request: URLRequest) -> Bool {
|
|
30
|
+
guard let scheme = request.url?.scheme else {
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
if !["http", "https"].contains(scheme) {
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
let isNewRequest = URLProtocol.property(
|
|
37
|
+
forKey: Self.REQUEST_ID,
|
|
38
|
+
in: request
|
|
39
|
+
) == nil
|
|
40
|
+
return isNewRequest
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
override init(
|
|
44
|
+
request: URLRequest,
|
|
45
|
+
cachedResponse: CachedURLResponse?,
|
|
46
|
+
client: URLProtocolClient?
|
|
47
|
+
) {
|
|
48
|
+
super.init(request: request, cachedResponse: cachedResponse, client: client)
|
|
49
|
+
// swiftlint:disable force_cast
|
|
50
|
+
let mutableRequest = request as! NSMutableURLRequest
|
|
51
|
+
// swiftlint:enable force_cast
|
|
52
|
+
let requestId = Self.requestIdProvider.create()
|
|
53
|
+
URLProtocol.setProperty(
|
|
54
|
+
requestId,
|
|
55
|
+
forKey: Self.REQUEST_ID,
|
|
56
|
+
in: mutableRequest
|
|
57
|
+
)
|
|
58
|
+
Self.delegate.willSendRequest(
|
|
59
|
+
requestId: requestId,
|
|
60
|
+
request: mutableRequest as URLRequest,
|
|
61
|
+
redirectResponse: nil
|
|
62
|
+
)
|
|
63
|
+
dataTask_ = urlSession.dataTask(with: mutableRequest as URLRequest)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public override class func canonicalRequest(for request: URLRequest) -> URLRequest {
|
|
67
|
+
request
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public override func startLoading() {
|
|
71
|
+
dataTask_?.resume()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public override func stopLoading() {
|
|
75
|
+
dataTask_?.cancel()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// MARK: URLSessionDataDelegate implementations
|
|
79
|
+
|
|
80
|
+
public func urlSession(_: URLSession, dataTask _: URLSessionDataTask, didReceive data: Data) {
|
|
81
|
+
client?.urlProtocol(self, didLoad: data)
|
|
82
|
+
if responseBody.length + data.count <= Self.MAX_BODY_SIZE {
|
|
83
|
+
responseBody.append(data)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public func urlSession(
|
|
88
|
+
_: URLSession,
|
|
89
|
+
dataTask: URLSessionDataTask,
|
|
90
|
+
didReceive response: URLResponse,
|
|
91
|
+
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void
|
|
92
|
+
) {
|
|
93
|
+
if let resp = response as? HTTPURLResponse,
|
|
94
|
+
let currentRequest = dataTask.currentRequest,
|
|
95
|
+
let requestId = URLProtocol.property(
|
|
96
|
+
forKey: Self.REQUEST_ID,
|
|
97
|
+
in: currentRequest
|
|
98
|
+
) as? String {
|
|
99
|
+
Self.delegate.didReceiveResponse(
|
|
100
|
+
requestId: requestId,
|
|
101
|
+
request: currentRequest,
|
|
102
|
+
response: resp
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
let contentType = resp.value(forHTTPHeaderField: "Content-Type")
|
|
106
|
+
responseIsText = (contentType?.starts(with: "text/") ?? false) || contentType == "application/json"
|
|
107
|
+
responseContentLength = resp.expectedContentLength
|
|
108
|
+
}
|
|
109
|
+
completionHandler(.allow)
|
|
110
|
+
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .allowed)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public func urlSession(_: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
|
|
114
|
+
if let error = error {
|
|
115
|
+
client?.urlProtocol(self, didFailWithError: error)
|
|
116
|
+
} else {
|
|
117
|
+
if responseContentLength > 0 && responseContentLength <= Self.MAX_BODY_SIZE,
|
|
118
|
+
let currentRequest = task.currentRequest,
|
|
119
|
+
let requestId = URLProtocol.property(
|
|
120
|
+
forKey: Self.REQUEST_ID,
|
|
121
|
+
in: currentRequest
|
|
122
|
+
) as? String {
|
|
123
|
+
Self.delegate.didReceiveResponseBody(
|
|
124
|
+
requestId: requestId, responseBody: responseBody as Data, isText: responseIsText)
|
|
125
|
+
}
|
|
126
|
+
client?.urlProtocolDidFinishLoading(self)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
public func urlSession(
|
|
131
|
+
_: URLSession,
|
|
132
|
+
task _: URLSessionTask,
|
|
133
|
+
willPerformHTTPRedirection response: HTTPURLResponse,
|
|
134
|
+
newRequest request: URLRequest,
|
|
135
|
+
completionHandler: @escaping (URLRequest?) -> Void
|
|
136
|
+
) {
|
|
137
|
+
if let requestId = URLProtocol.property(forKey: Self.REQUEST_ID, in: request) as? String {
|
|
138
|
+
Self.delegate.willSendRequest(
|
|
139
|
+
requestId: requestId,
|
|
140
|
+
request: request,
|
|
141
|
+
redirectResponse: response
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
completionHandler(request)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
Data structure to save the response for redirection
|
|
149
|
+
*/
|
|
150
|
+
private struct RedirectResponse {
|
|
151
|
+
let requestId: String
|
|
152
|
+
let redirectResponse: HTTPURLResponse
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
A helper class to create a unique request ID
|
|
157
|
+
*/
|
|
158
|
+
private struct RequestIdProvider {
|
|
159
|
+
private var value: UInt64 = 0
|
|
160
|
+
|
|
161
|
+
mutating func create() -> String {
|
|
162
|
+
// We could ensure the increment thread safety,
|
|
163
|
+
// because we access this function from the same thread (com.apple.CFNetwork.CustomProtocols).
|
|
164
|
+
value += 1
|
|
165
|
+
return String(value)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
The delegate to dispatch network request events
|
|
172
|
+
*/
|
|
173
|
+
@objc(EXRequestInterceptorProtocolDelegate)
|
|
174
|
+
protocol ExpoRequestInterceptorProtocolDelegate {
|
|
175
|
+
@objc
|
|
176
|
+
func willSendRequest(requestId: String, request: URLRequest, redirectResponse: HTTPURLResponse?)
|
|
177
|
+
|
|
178
|
+
@objc
|
|
179
|
+
func didReceiveResponse(requestId: String, request: URLRequest, response: HTTPURLResponse)
|
|
180
|
+
|
|
181
|
+
@objc
|
|
182
|
+
func didReceiveResponseBody(requestId: String, responseBody: Data, isText: Bool)
|
|
183
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Copyright 2015-present 650 Industries. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import Foundation
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
`URLRequest.httpBodyData()` extension to read the underlying `httpBodyStream` as Data.
|
|
7
|
+
*/
|
|
8
|
+
extension URLRequest {
|
|
9
|
+
func httpBodyData(limit: Int = ExpoRequestInterceptorProtocol.MAX_BODY_SIZE) -> Data? {
|
|
10
|
+
if let httpBody = self.httpBody {
|
|
11
|
+
return httpBody
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if let contentLength = self.allHTTPHeaderFields?["Content-Length"],
|
|
15
|
+
let contentLengthInt = Int(contentLength),
|
|
16
|
+
contentLengthInt > limit {
|
|
17
|
+
return nil
|
|
18
|
+
}
|
|
19
|
+
guard let stream = self.httpBodyStream else {
|
|
20
|
+
return nil
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let bufferSize: Int = 8192
|
|
24
|
+
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
|
|
25
|
+
|
|
26
|
+
stream.open()
|
|
27
|
+
defer {
|
|
28
|
+
buffer.deallocate()
|
|
29
|
+
stream.close()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
var data = Data()
|
|
33
|
+
while stream.hasBytesAvailable {
|
|
34
|
+
let chunkSize = stream.read(buffer, maxLength: bufferSize)
|
|
35
|
+
if data.count + chunkSize > limit {
|
|
36
|
+
return nil
|
|
37
|
+
}
|
|
38
|
+
data.append(buffer, count: chunkSize)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return data
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Property name of the core object in the global scope of the Expo JS runtime.
|
|
3
|
+
*/
|
|
4
|
+
private let coreObjectPropertyName = "expo"
|
|
5
|
+
|
|
6
|
+
@objc(ExpoRuntime)
|
|
7
|
+
public final class ExpoRuntime: JavaScriptRuntime {
|
|
8
|
+
/**
|
|
9
|
+
The core object of the Expo runtime that is used to scope native Expo-specific functionalities.
|
|
10
|
+
It gets installed into the runtime as the `global.expo` object.
|
|
11
|
+
*/
|
|
12
|
+
@objc
|
|
13
|
+
public private(set) var coreObject: JavaScriptObject?
|
|
14
|
+
|
|
15
|
+
internal func initializeCoreObject(_ coreObject: JavaScriptObject) throws {
|
|
16
|
+
guard self.coreObject == nil else {
|
|
17
|
+
throw CoreObjectInitializedException()
|
|
18
|
+
}
|
|
19
|
+
self.coreObject = coreObject
|
|
20
|
+
global().defineProperty(coreObjectPropertyName, value: coreObject, options: .enumerable)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private final class CoreObjectInitializedException: Exception {
|
|
25
|
+
override var reason: String {
|
|
26
|
+
"The core Expo object was already initialized"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// The core module that describes the `global.expo` object.
|
|
2
|
+
internal final class CoreModule: Module {
|
|
3
|
+
internal func definition() -> ModuleDefinition {
|
|
4
|
+
// Nothing so far, but eventually we will expose some common classes
|
|
5
|
+
// and maybe even the `modules` host object.
|
|
6
|
+
}
|
|
7
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Shared object (ref) that holds a pointer to any native object. Allows passing references
|
|
3
|
+
to native instances among different independent libraries.
|
|
4
|
+
*/
|
|
5
|
+
open class SharedRef<PointerType>: SharedObject {
|
|
6
|
+
public let pointer: PointerType
|
|
7
|
+
|
|
8
|
+
init(_ pointer: PointerType) {
|
|
9
|
+
self.pointer = pointer
|
|
10
|
+
super.init()
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -7,7 +7,7 @@ import React
|
|
|
7
7
|
but it also simplifies capturing the view config so we can omit some reflections that React Native executes.
|
|
8
8
|
*/
|
|
9
9
|
@objc(EXComponentData)
|
|
10
|
-
public final class ComponentData:
|
|
10
|
+
public final class ComponentData: RCTComponentDataSwiftAdapter {
|
|
11
11
|
/**
|
|
12
12
|
Weak pointer to the holder of a module that the component data was created for.
|
|
13
13
|
*/
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import ExpoModulesTestCore
|
|
2
|
+
|
|
3
|
+
@testable import ExpoModulesCore
|
|
4
|
+
|
|
5
|
+
final class CoreModuleSpec: ExpoSpec {
|
|
6
|
+
override func spec() {
|
|
7
|
+
let appContext = AppContext.create()
|
|
8
|
+
let runtime = try! appContext.runtime
|
|
9
|
+
|
|
10
|
+
describe("core module") {
|
|
11
|
+
it("is initialized") {
|
|
12
|
+
expect(appContext.coreModule).notTo(beNil())
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe("core object") {
|
|
17
|
+
it("is initialized") {
|
|
18
|
+
expect(runtime.coreObject).notTo(beNil())
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
it("is installed to global scope") {
|
|
22
|
+
let coreObjectValue = try runtime.eval("expo")
|
|
23
|
+
expect(coreObjectValue.kind) == .object
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|