expo-modules-core 1.12.15 → 1.12.17
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 +19 -0
- package/android/build.gradle +2 -2
- package/android/src/main/cpp/JSIContext.cpp +5 -42
- package/android/src/main/cpp/JSIContext.h +11 -0
- package/android/src/main/cpp/ThreadSafeJNIGlobalRef.h +49 -0
- package/android/src/main/java/expo/modules/kotlin/types/TypeConverterProvider.kt +3 -3
- package/ios/DevTools/ExpoRequestCdpInterceptor.swift +1 -1
- package/ios/DevTools/ExpoRequestInterceptorProtocol.swift +131 -28
- package/ios/FileSystemUtilities/FileSystemLegacyUtilities.swift +1 -1
- package/ios/Legacy/Services/EXReactNativeAdapter.mm +0 -10
- package/ios/Tests/FileSystemLegacyUtilitiesSpec.swift +29 -0
- package/ios/Utilities.swift +29 -3
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -10,11 +10,30 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 1.12.17 — 2024-06-27
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug fixes
|
|
16
|
+
|
|
17
|
+
- [Android] Reduce the number of global references to JSIContext. ([#29936](https://github.com/expo/expo/pull/29936) by [@lukmccall](https://github.com/lukmccall))
|
|
18
|
+
- Fixed `getPathPermissions` permission error for local path with spaces on iOS 16 and older. ([#29958](https://github.com/expo/expo/pull/29958) by [@kudo](https://github.com/kudo))
|
|
19
|
+
- [iOS] Fixed broken `addUIBlock` and `executeUIBlock` on New Architecture mode. ([#30030](https://github.com/expo/expo/pull/30030) by [@kudo](https://github.com/kudo))
|
|
20
|
+
|
|
21
|
+
### 💡 Others
|
|
22
|
+
|
|
23
|
+
- [iOS] Exposed `Utilities` class for Expo Modules common tasks. ([#29945](https://github.com/expo/expo/pull/29945) by [@kudo](https://github.com/kudo))
|
|
24
|
+
|
|
25
|
+
## 1.12.16 — 2024-06-20
|
|
26
|
+
|
|
27
|
+
### 🐛 Bug fixes
|
|
28
|
+
|
|
29
|
+
- Fixed resource leakage from `ExpoRequestInterceptorProtocol`. ([#29798](https://github.com/expo/expo/pull/29798) by [@kudo](https://github.com/kudo))
|
|
30
|
+
|
|
13
31
|
## 1.12.15 — 2024-06-13
|
|
14
32
|
|
|
15
33
|
### 🐛 Bug fixes
|
|
16
34
|
|
|
17
35
|
- Fixed reload crash on Android. ([#29593](https://github.com/expo/expo/pull/29593) by [@kudo](https://github.com/kudo))
|
|
36
|
+
- [Android] Fixed converting from `null` to `Record` sometimes didn't work as expected. ([#29508](https://github.com/expo/expo/pull/29508) by [@lukmccall](https://github.com/lukmccall))
|
|
18
37
|
|
|
19
38
|
## 1.12.14 — 2024-06-06
|
|
20
39
|
|
package/android/build.gradle
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
apply plugin: 'com.android.library'
|
|
2
2
|
|
|
3
3
|
group = 'host.exp.exponent'
|
|
4
|
-
version = '1.12.
|
|
4
|
+
version = '1.12.17'
|
|
5
5
|
|
|
6
6
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
7
7
|
apply from: expoModulesCorePlugin
|
|
@@ -63,7 +63,7 @@ android {
|
|
|
63
63
|
defaultConfig {
|
|
64
64
|
consumerProguardFiles 'proguard-rules.pro'
|
|
65
65
|
versionCode 1
|
|
66
|
-
versionName "1.12.
|
|
66
|
+
versionName "1.12.17"
|
|
67
67
|
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled.toString()
|
|
68
68
|
|
|
69
69
|
testInstrumentationRunner "expo.modules.TestRunner"
|
|
@@ -29,42 +29,6 @@ namespace expo {
|
|
|
29
29
|
|
|
30
30
|
#endif
|
|
31
31
|
|
|
32
|
-
/*
|
|
33
|
-
* A wrapper for a global reference that can be deallocated on any thread.
|
|
34
|
-
* It should be used with smart pointer. That structure can't be copied or moved.
|
|
35
|
-
*/
|
|
36
|
-
template <typename T>
|
|
37
|
-
class ThreadSafeJNIGlobalRef {
|
|
38
|
-
public:
|
|
39
|
-
ThreadSafeJNIGlobalRef(jobject globalRef) : globalRef(globalRef) {}
|
|
40
|
-
ThreadSafeJNIGlobalRef(const ThreadSafeJNIGlobalRef &other) = delete;
|
|
41
|
-
ThreadSafeJNIGlobalRef(ThreadSafeJNIGlobalRef &&other) = delete;
|
|
42
|
-
ThreadSafeJNIGlobalRef &operator=(const ThreadSafeJNIGlobalRef &other) = delete;
|
|
43
|
-
ThreadSafeJNIGlobalRef &operator=(ThreadSafeJNIGlobalRef &&other) = delete;
|
|
44
|
-
|
|
45
|
-
void use(std::function<void(jni::alias_ref<T> globalRef)> &&action) {
|
|
46
|
-
if (globalRef == nullptr) {
|
|
47
|
-
throw std::runtime_error("ThreadSafeJNIGlobalRef: globalRef is null");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
jni::ThreadScope::WithClassLoader([this, action = std::move(action)]() {
|
|
51
|
-
jni::alias_ref<jobject> aliasRef = jni::wrap_alias(globalRef);
|
|
52
|
-
jni::alias_ref<T> jsiContextRef = jni::static_ref_cast<T>(aliasRef);
|
|
53
|
-
action(jsiContextRef);
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
~ThreadSafeJNIGlobalRef() {
|
|
58
|
-
if (globalRef != nullptr) {
|
|
59
|
-
jni::ThreadScope::WithClassLoader([this] {
|
|
60
|
-
jni::Environment::current()->DeleteGlobalRef(this->globalRef);
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
jobject globalRef;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
32
|
jni::local_ref<JSIContext::jhybriddata>
|
|
69
33
|
JSIContext::initHybrid(jni::alias_ref<jhybridobject> jThis) {
|
|
70
34
|
return makeCxxInstance(jThis);
|
|
@@ -90,7 +54,10 @@ void JSIContext::registerNatives() {
|
|
|
90
54
|
}
|
|
91
55
|
|
|
92
56
|
JSIContext::JSIContext(jni::alias_ref<jhybridobject> jThis)
|
|
93
|
-
: javaPart_(jni::make_global(jThis))
|
|
57
|
+
: javaPart_(jni::make_global(jThis)),
|
|
58
|
+
threadSafeJThis(std::make_shared<ThreadSafeJNIGlobalRef<JSIContext::javaobject>>(
|
|
59
|
+
jni::Environment::current()->NewGlobalRef(javaPart_.get())
|
|
60
|
+
)) {}
|
|
94
61
|
|
|
95
62
|
JSIContext::~JSIContext() {
|
|
96
63
|
if (runtimeHolder) {
|
|
@@ -368,15 +335,11 @@ void JSIContext::jniSetNativeStateForSharedObject(
|
|
|
368
335
|
int id,
|
|
369
336
|
jni::alias_ref<JavaScriptObject::javaobject> jsObject
|
|
370
337
|
) {
|
|
371
|
-
auto threadSafeRef = std::make_shared<ThreadSafeJNIGlobalRef<JSIContext::javaobject>>(
|
|
372
|
-
jni::Environment::current()->NewGlobalRef(javaPart_.get())
|
|
373
|
-
);
|
|
374
|
-
|
|
375
338
|
auto nativeState = std::make_shared<expo::SharedObject::NativeState>(
|
|
376
339
|
id,
|
|
377
340
|
// We can't predict the order of deallocation of the JSIContext and the SharedObject.
|
|
378
341
|
// So we need to pass a new ref to retain the JSIContext to make sure it's not deallocated before the SharedObject.
|
|
379
|
-
[threadSafeRef =
|
|
342
|
+
[threadSafeRef = threadSafeJThis](const SharedObject::ObjectId objectId) {
|
|
380
343
|
threadSafeRef->use([objectId](jni::alias_ref<JSIContext::javaobject> globalRef) {
|
|
381
344
|
JSIContext::deleteSharedObject(globalRef, objectId);
|
|
382
345
|
});
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
#include "JavaReferencesCache.h"
|
|
10
10
|
#include "JSReferencesCache.h"
|
|
11
11
|
#include "JNIDeallocator.h"
|
|
12
|
+
#include "ThreadSafeJNIGlobalRef.h"
|
|
12
13
|
|
|
13
14
|
#include <fbjni/fbjni.h>
|
|
14
15
|
#include <jsi/jsi.h>
|
|
@@ -149,10 +150,20 @@ public:
|
|
|
149
150
|
|
|
150
151
|
private:
|
|
151
152
|
friend HybridBase;
|
|
153
|
+
|
|
154
|
+
/*
|
|
155
|
+
* We store two global references to the Java part of the JSIContext.
|
|
156
|
+
* However, one is wrapped in additional abstraction to make it thread-safe,
|
|
157
|
+
* which increase the access time. For most operations, we should use the bare reference.
|
|
158
|
+
* Only for operations that are executed on different threads that aren't attached to JVM,
|
|
159
|
+
* we should use the thread-safe reference.
|
|
160
|
+
*/
|
|
152
161
|
jni::global_ref<JSIContext::javaobject> javaPart_;
|
|
162
|
+
std::shared_ptr<ThreadSafeJNIGlobalRef<JSIContext::javaobject>> threadSafeJThis;
|
|
153
163
|
|
|
154
164
|
bool wasDeallocated_ = false;
|
|
155
165
|
|
|
166
|
+
|
|
156
167
|
explicit JSIContext(jni::alias_ref<jhybridobject> jThis);
|
|
157
168
|
|
|
158
169
|
inline jni::local_ref<JavaScriptModuleObject::javaobject>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Copyright © 2021-present 650 Industries, Inc. (aka Expo)
|
|
2
|
+
|
|
3
|
+
#pragma once
|
|
4
|
+
|
|
5
|
+
#include <fbjni/fbjni.h>
|
|
6
|
+
#include <android/log.h>
|
|
7
|
+
|
|
8
|
+
namespace jni = facebook::jni;
|
|
9
|
+
|
|
10
|
+
namespace expo {
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
* A wrapper for a global reference that can be deallocated on any thread.
|
|
14
|
+
* It should be used with smart pointer. That structure can't be copied or moved.
|
|
15
|
+
*/
|
|
16
|
+
template<typename T>
|
|
17
|
+
class ThreadSafeJNIGlobalRef {
|
|
18
|
+
public:
|
|
19
|
+
ThreadSafeJNIGlobalRef(jobject globalRef) : globalRef(globalRef) {}
|
|
20
|
+
ThreadSafeJNIGlobalRef(const ThreadSafeJNIGlobalRef &other) = delete;
|
|
21
|
+
ThreadSafeJNIGlobalRef(ThreadSafeJNIGlobalRef &&other) = delete;
|
|
22
|
+
ThreadSafeJNIGlobalRef &operator=(const ThreadSafeJNIGlobalRef &other) = delete;
|
|
23
|
+
ThreadSafeJNIGlobalRef &operator=(ThreadSafeJNIGlobalRef &&other) = delete;
|
|
24
|
+
|
|
25
|
+
void use(std::function<void(jni::alias_ref<T> globalRef)> &&action) {
|
|
26
|
+
if (globalRef == nullptr) {
|
|
27
|
+
__android_log_print(ANDROID_LOG_WARN, "ExpoModulesCore", "ThreadSafeJNIGlobalRef was used after deallocation.");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
jni::ThreadScope::WithClassLoader([this, action = std::move(action)]() {
|
|
32
|
+
jni::alias_ref<jobject> aliasRef = jni::wrap_alias(globalRef);
|
|
33
|
+
jni::alias_ref<T> jsiContextRef = jni::static_ref_cast<T>(aliasRef);
|
|
34
|
+
action(jsiContextRef);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
~ThreadSafeJNIGlobalRef() {
|
|
39
|
+
if (globalRef != nullptr) {
|
|
40
|
+
jni::ThreadScope::WithClassLoader([this] {
|
|
41
|
+
jni::Environment::current()->DeleteGlobalRef(this->globalRef);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
jobject globalRef;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
} // namespace expo
|
|
@@ -73,7 +73,7 @@ object TypeConverterProviderImpl : TypeConverterProvider {
|
|
|
73
73
|
private val cachedConverters = createCachedConverters(false)
|
|
74
74
|
private val nullableCachedConverters = createCachedConverters(true)
|
|
75
75
|
|
|
76
|
-
private val cachedRecordConverters = mutableMapOf<
|
|
76
|
+
private val cachedRecordConverters = mutableMapOf<KType, TypeConverter<*>>()
|
|
77
77
|
|
|
78
78
|
private fun getCachedConverter(inputType: KType): TypeConverter<*>? {
|
|
79
79
|
return if (inputType.isMarkedNullable) {
|
|
@@ -116,14 +116,14 @@ object TypeConverterProviderImpl : TypeConverterProvider {
|
|
|
116
116
|
return EnumTypeConverter(kClass as KClass<Enum<*>>, type.isMarkedNullable)
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
val cachedConverter = cachedRecordConverters[
|
|
119
|
+
val cachedConverter = cachedRecordConverters[type]
|
|
120
120
|
if (cachedConverter != null) {
|
|
121
121
|
return cachedConverter
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
if (Record::class.java.isAssignableFrom(jClass)) {
|
|
125
125
|
val converter = RecordTypeConverter<Record>(this, type)
|
|
126
|
-
cachedRecordConverters[
|
|
126
|
+
cachedRecordConverters[type] = converter
|
|
127
127
|
return converter
|
|
128
128
|
}
|
|
129
129
|
|
|
@@ -9,7 +9,7 @@ import Foundation
|
|
|
9
9
|
@objc(EXRequestCdpInterceptor)
|
|
10
10
|
public final class ExpoRequestCdpInterceptor: NSObject, ExpoRequestInterceptorProtocolDelegate {
|
|
11
11
|
private var delegate: ExpoRequestCdpInterceptorDelegate?
|
|
12
|
-
|
|
12
|
+
public var dispatchQueue = DispatchQueue(label: "expo.requestCdpInterceptor")
|
|
13
13
|
|
|
14
14
|
override private init() {}
|
|
15
15
|
|
|
@@ -7,13 +7,13 @@ import Foundation
|
|
|
7
7
|
*/
|
|
8
8
|
@objc(EXRequestInterceptorProtocol)
|
|
9
9
|
public final class ExpoRequestInterceptorProtocol: URLProtocol, URLSessionDataDelegate {
|
|
10
|
-
private static let
|
|
11
|
-
private static
|
|
12
|
-
private lazy var urlSession = URLSession(
|
|
10
|
+
private static let sessionDelegateProxy = URLSessionSessionDelegateProxy()
|
|
11
|
+
private static let urlSession = URLSession(
|
|
13
12
|
configuration: URLSessionConfiguration.default,
|
|
14
|
-
delegate:
|
|
13
|
+
delegate: sessionDelegateProxy,
|
|
15
14
|
delegateQueue: nil
|
|
16
15
|
)
|
|
16
|
+
private var requestId: String?
|
|
17
17
|
private var dataTask_: URLSessionDataTask?
|
|
18
18
|
private let responseBody = NSMutableData()
|
|
19
19
|
private var responseBodyExceedsLimit = false
|
|
@@ -32,11 +32,10 @@ public final class ExpoRequestInterceptorProtocol: URLProtocol, URLSessionDataDe
|
|
|
32
32
|
if !["http", "https"].contains(scheme) {
|
|
33
33
|
return false
|
|
34
34
|
}
|
|
35
|
-
|
|
36
|
-
forKey:
|
|
35
|
+
return URLProtocol.property(
|
|
36
|
+
forKey: REQUEST_ID,
|
|
37
37
|
in: request
|
|
38
38
|
) == nil
|
|
39
|
-
return isNewRequest
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
override init(
|
|
@@ -48,13 +47,16 @@ public final class ExpoRequestInterceptorProtocol: URLProtocol, URLSessionDataDe
|
|
|
48
47
|
// swiftlint:disable force_cast
|
|
49
48
|
let mutableRequest = request as! NSMutableURLRequest
|
|
50
49
|
// swiftlint:enable force_cast
|
|
51
|
-
|
|
50
|
+
self.requestId = Self.sessionDelegateProxy.addDelegate(delegate: self)
|
|
51
|
+
guard let requestId else {
|
|
52
|
+
fatalError("requestId should not be nil.")
|
|
53
|
+
}
|
|
52
54
|
URLProtocol.setProperty(
|
|
53
55
|
requestId,
|
|
54
|
-
forKey:
|
|
56
|
+
forKey: REQUEST_ID,
|
|
55
57
|
in: mutableRequest
|
|
56
58
|
)
|
|
57
|
-
let dataTask = urlSession.dataTask(with: mutableRequest as URLRequest)
|
|
59
|
+
let dataTask = Self.urlSession.dataTask(with: mutableRequest as URLRequest)
|
|
58
60
|
Self.delegate.willSendRequest(
|
|
59
61
|
requestId: requestId,
|
|
60
62
|
task: dataTask,
|
|
@@ -74,6 +76,9 @@ public final class ExpoRequestInterceptorProtocol: URLProtocol, URLSessionDataDe
|
|
|
74
76
|
|
|
75
77
|
public override func stopLoading() {
|
|
76
78
|
dataTask_?.cancel()
|
|
79
|
+
if let requestId {
|
|
80
|
+
Self.sessionDelegateProxy.removeDelegate(requestId: requestId)
|
|
81
|
+
}
|
|
77
82
|
}
|
|
78
83
|
|
|
79
84
|
// MARK: URLSessionDataDelegate implementations
|
|
@@ -91,12 +96,8 @@ public final class ExpoRequestInterceptorProtocol: URLProtocol, URLSessionDataDe
|
|
|
91
96
|
if let error = error {
|
|
92
97
|
client?.urlProtocol(self, didFailWithError: error)
|
|
93
98
|
} else {
|
|
94
|
-
if let
|
|
95
|
-
let
|
|
96
|
-
let requestId = URLProtocol.property(
|
|
97
|
-
forKey: Self.REQUEST_ID,
|
|
98
|
-
in: currentRequest
|
|
99
|
-
) as? String {
|
|
99
|
+
if let response = task.response as? HTTPURLResponse,
|
|
100
|
+
let requestId {
|
|
100
101
|
let contentType = response.value(forHTTPHeaderField: "Content-Type")
|
|
101
102
|
let isText = (contentType?.starts(with: "text/") ?? false) || contentType == "application/json"
|
|
102
103
|
Self.delegate.didReceiveResponse(
|
|
@@ -123,7 +124,7 @@ public final class ExpoRequestInterceptorProtocol: URLProtocol, URLSessionDataDe
|
|
|
123
124
|
newRequest request: URLRequest,
|
|
124
125
|
completionHandler: @escaping (URLRequest?) -> Void
|
|
125
126
|
) {
|
|
126
|
-
if let requestId
|
|
127
|
+
if let requestId {
|
|
127
128
|
Self.delegate.willSendRequest(
|
|
128
129
|
requestId: requestId,
|
|
129
130
|
task: task,
|
|
@@ -141,6 +142,118 @@ public final class ExpoRequestInterceptorProtocol: URLProtocol, URLSessionDataDe
|
|
|
141
142
|
let requestId: String
|
|
142
143
|
let redirectResponse: HTTPURLResponse
|
|
143
144
|
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
The delegate to dispatch network request events
|
|
149
|
+
*/
|
|
150
|
+
@objc(EXRequestInterceptorProtocolDelegate)
|
|
151
|
+
protocol ExpoRequestInterceptorProtocolDelegate {
|
|
152
|
+
@objc
|
|
153
|
+
func willSendRequest(requestId: String, task: URLSessionTask, request: URLRequest, redirectResponse: HTTPURLResponse?)
|
|
154
|
+
|
|
155
|
+
@objc
|
|
156
|
+
func didReceiveResponse(requestId: String, task: URLSessionTask, responseBody: Data, isText: Bool, responseBodyExceedsLimit: Bool)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
Shared URLSessionDataDelegate instance and delete calls back to ExpoRequestInterceptorProtocol instances.
|
|
161
|
+
*/
|
|
162
|
+
private class URLSessionSessionDelegateProxy: NSObject, URLSessionDataDelegate {
|
|
163
|
+
private var requestIdProvider = RequestIdProvider()
|
|
164
|
+
private var delegateMap: [String: URLSessionDataDelegate] = [:]
|
|
165
|
+
private let dispatchQueue = ExpoRequestCdpInterceptor.shared.dispatchQueue
|
|
166
|
+
|
|
167
|
+
func addDelegate(delegate: URLSessionDataDelegate) -> String {
|
|
168
|
+
let requestId = self.requestIdProvider.create()
|
|
169
|
+
self.dispatchQueue.async {
|
|
170
|
+
self.delegateMap[requestId] = delegate
|
|
171
|
+
}
|
|
172
|
+
return requestId
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
func removeDelegate(requestId: String) {
|
|
176
|
+
self.dispatchQueue.async {
|
|
177
|
+
self.delegateMap.removeValue(forKey: requestId)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private func getRequestId(task: URLSessionTask) -> String? {
|
|
182
|
+
if let currentRequest = task.currentRequest,
|
|
183
|
+
let requestId = URLProtocol.property(
|
|
184
|
+
forKey: REQUEST_ID,
|
|
185
|
+
in: currentRequest
|
|
186
|
+
) as? String {
|
|
187
|
+
return requestId
|
|
188
|
+
}
|
|
189
|
+
return nil
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private func getDelegate(requestId: String) -> URLSessionDataDelegate? {
|
|
193
|
+
return self.dispatchQueue.sync {
|
|
194
|
+
return self.delegateMap[requestId]
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private func getDelegate(task: URLSessionTask) -> URLSessionDataDelegate? {
|
|
199
|
+
guard let requestId = self.getRequestId(task: task) else {
|
|
200
|
+
return nil
|
|
201
|
+
}
|
|
202
|
+
return self.getDelegate(requestId: requestId)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// MARK: URLSessionDataDelegate implementations
|
|
206
|
+
|
|
207
|
+
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive: Data) {
|
|
208
|
+
if let delegate = getDelegate(task: dataTask) {
|
|
209
|
+
delegate.urlSession?(
|
|
210
|
+
session,
|
|
211
|
+
dataTask: dataTask,
|
|
212
|
+
didReceive: didReceive)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError: Error?) {
|
|
217
|
+
if let requestId = self.getRequestId(task: task), let delegate = getDelegate(requestId: requestId) {
|
|
218
|
+
delegate.urlSession?(
|
|
219
|
+
session,
|
|
220
|
+
task: task,
|
|
221
|
+
didCompleteWithError: didCompleteWithError)
|
|
222
|
+
self.removeDelegate(requestId: requestId)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
func urlSession(
|
|
227
|
+
_ session: URLSession,
|
|
228
|
+
dataTask: URLSessionDataTask,
|
|
229
|
+
didReceive: URLResponse,
|
|
230
|
+
completionHandler: @escaping (URLSession.ResponseDisposition) -> Void
|
|
231
|
+
) {
|
|
232
|
+
if let delegate = getDelegate(task: dataTask) {
|
|
233
|
+
delegate.urlSession?(
|
|
234
|
+
session,
|
|
235
|
+
dataTask: dataTask,
|
|
236
|
+
didReceive: didReceive,
|
|
237
|
+
completionHandler: completionHandler)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
func urlSession(
|
|
242
|
+
_ session: URLSession,
|
|
243
|
+
task: URLSessionTask,
|
|
244
|
+
willPerformHTTPRedirection: HTTPURLResponse,
|
|
245
|
+
newRequest: URLRequest,
|
|
246
|
+
completionHandler: @escaping (URLRequest?) -> Void
|
|
247
|
+
) {
|
|
248
|
+
if let delegate = getDelegate(task: task) {
|
|
249
|
+
delegate.urlSession?(
|
|
250
|
+
session,
|
|
251
|
+
task: task,
|
|
252
|
+
willPerformHTTPRedirection: willPerformHTTPRedirection,
|
|
253
|
+
newRequest: newRequest,
|
|
254
|
+
completionHandler: completionHandler)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
144
257
|
|
|
145
258
|
/**
|
|
146
259
|
A helper class to create a unique request ID
|
|
@@ -157,14 +270,4 @@ public final class ExpoRequestInterceptorProtocol: URLProtocol, URLSessionDataDe
|
|
|
157
270
|
}
|
|
158
271
|
}
|
|
159
272
|
|
|
160
|
-
|
|
161
|
-
The delegate to dispatch network request events
|
|
162
|
-
*/
|
|
163
|
-
@objc(EXRequestInterceptorProtocolDelegate)
|
|
164
|
-
protocol ExpoRequestInterceptorProtocolDelegate {
|
|
165
|
-
@objc
|
|
166
|
-
func willSendRequest(requestId: String, task: URLSessionTask, request: URLRequest, redirectResponse: HTTPURLResponse?)
|
|
167
|
-
|
|
168
|
-
@objc
|
|
169
|
-
func didReceiveResponse(requestId: String, task: URLSessionTask, responseBody: Data, isText: Bool, responseBodyExceedsLimit: Bool)
|
|
170
|
-
}
|
|
273
|
+
private let REQUEST_ID = "ExpoRequestInterceptorProtocol.requestId"
|
|
@@ -82,7 +82,7 @@ public class FileSystemLegacyUtilities: NSObject, EXInternalModule, EXFileSystem
|
|
|
82
82
|
|
|
83
83
|
@objc
|
|
84
84
|
public func getPathPermissions(_ path: String) -> EXFileSystemPermissionFlags {
|
|
85
|
-
guard let url =
|
|
85
|
+
guard let url = convertToUrl(string: path) else {
|
|
86
86
|
return []
|
|
87
87
|
}
|
|
88
88
|
let permissionsForInternalDirectories = getInternalPathPermissions(url)
|
|
@@ -292,12 +292,7 @@ EX_REGISTER_MODULE();
|
|
|
292
292
|
|
|
293
293
|
dispatch_async(RCTGetUIManagerQueue(), ^{
|
|
294
294
|
[uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
295
|
-
#if RCT_NEW_ARCH_ENABLED
|
|
296
|
-
UIView<RCTComponentViewProtocol> *componentView = [uiManager viewForReactTag:(NSNumber *)viewId];
|
|
297
|
-
UIView *view = [(ExpoFabricViewObjC *)componentView contentView];
|
|
298
|
-
#else
|
|
299
295
|
UIView *view = [uiManager viewForReactTag:(NSNumber *)viewId];
|
|
300
|
-
#endif
|
|
301
296
|
block(view);
|
|
302
297
|
}];
|
|
303
298
|
});
|
|
@@ -309,12 +304,7 @@ EX_REGISTER_MODULE();
|
|
|
309
304
|
|
|
310
305
|
dispatch_async(RCTGetUIManagerQueue(), ^{
|
|
311
306
|
[uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
312
|
-
#if RCT_NEW_ARCH_ENABLED
|
|
313
|
-
UIView<RCTComponentViewProtocol> *componentView = [uiManager viewForReactTag:(NSNumber *)viewId];
|
|
314
|
-
UIView *view = [(ExpoFabricViewObjC *)componentView contentView];
|
|
315
|
-
#else
|
|
316
307
|
UIView *view = [uiManager viewForReactTag:(NSNumber *)viewId];
|
|
317
|
-
#endif
|
|
318
308
|
block(view);
|
|
319
309
|
}];
|
|
320
310
|
[uiManager setNeedsLayout];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Copyright 2015-present 650 Industries. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import ExpoModulesTestCore
|
|
4
|
+
|
|
5
|
+
@testable import ExpoModulesCore
|
|
6
|
+
|
|
7
|
+
final class FileSystemLegacyUtilitiesSpec: ExpoSpec {
|
|
8
|
+
override class func spec() {
|
|
9
|
+
let fsUtils = FileSystemLegacyUtilities()
|
|
10
|
+
|
|
11
|
+
describe("getPathPermissions") {
|
|
12
|
+
it("should return read/write permissions for filePath with `file:` scheme") {
|
|
13
|
+
let dirUrl = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
|
|
14
|
+
let fileUrl = dirUrl.appendingPathComponent("dir/test.txt")
|
|
15
|
+
let filePath = fileUrl.absoluteString
|
|
16
|
+
expect(filePath.starts(with: "file:")) == true
|
|
17
|
+
expect(fsUtils.getPathPermissions(filePath)) == [.read, .write]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
it("should return read/write permissions for filePath without `file:` scheme") {
|
|
21
|
+
let dirUrl = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
|
|
22
|
+
let fileUrl = dirUrl.appendingPathComponent("dir/test.txt")
|
|
23
|
+
let filePath = fileUrl.path
|
|
24
|
+
expect(filePath.starts(with: "file:")) == false
|
|
25
|
+
expect(fsUtils.getPathPermissions(filePath)) == [.read, .write]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
package/ios/Utilities.swift
CHANGED
|
@@ -60,11 +60,37 @@ internal func isFileUrlPath(_ path: String) -> Bool {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
internal func convertToUrl(string value: String) -> URL? {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
let url: URL?
|
|
64
|
+
if #available(iOS 17, *) {
|
|
65
|
+
// URL(string:) supports RFC 3986 as URLComponents from iOS 17
|
|
66
|
+
url = URL(string: value)
|
|
67
|
+
} else if #available(iOS 16, *) {
|
|
68
|
+
// URLComponents parses and constructs URLs according to RFC 3986.
|
|
69
|
+
// For some unusual urls URL(string:) will fail incorrectly
|
|
70
|
+
url = URLComponents(string: value)?.url ?? URL(string: value)
|
|
71
|
+
} else {
|
|
72
|
+
// URLComponents on iOS 15 and lower does not well support RFC 3986.
|
|
73
|
+
// We have to fallback URL(fileURLWithPath:) first.
|
|
74
|
+
url = value.hasPrefix("/")
|
|
75
|
+
? URL(fileURLWithPath: value)
|
|
76
|
+
: URLComponents(string: value)?.url ?? URL(string: value)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
guard let url else {
|
|
66
80
|
return nil
|
|
67
81
|
}
|
|
68
82
|
// If it has no scheme, we assume it was the file path which needs to be recreated to be recognized as the file url.
|
|
69
83
|
return url.scheme != nil ? url : URL(fileURLWithPath: value)
|
|
70
84
|
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
A collection of utility functions for various Expo Modules common tasks.
|
|
88
|
+
*/
|
|
89
|
+
public struct Utilities {
|
|
90
|
+
/**
|
|
91
|
+
Converts a `String` to a `URL`.
|
|
92
|
+
*/
|
|
93
|
+
public static func urlFrom(string: String) -> URL? {
|
|
94
|
+
return convertToUrl(string: string)
|
|
95
|
+
}
|
|
96
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-modules-core",
|
|
3
|
-
"version": "1.12.
|
|
3
|
+
"version": "1.12.17",
|
|
4
4
|
"description": "The core of Expo Modules architecture",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
"@testing-library/react-hooks": "^7.0.1",
|
|
45
45
|
"expo-module-scripts": "^3.0.0"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "09b2d97bbc0f70f7c811ff9b6c9ad8808c5ad84b"
|
|
48
48
|
}
|