expo-modules-core 1.12.16 → 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 CHANGED
@@ -10,6 +10,18 @@
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
+
13
25
  ## 1.12.16 — 2024-06-20
14
26
 
15
27
  ### 🐛 Bug fixes
@@ -1,7 +1,7 @@
1
1
  apply plugin: 'com.android.library'
2
2
 
3
3
  group = 'host.exp.exponent'
4
- version = '1.12.16'
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.16"
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 = std::move(threadSafeRef)](const SharedObject::ObjectId objectId) {
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
@@ -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 = URL(string: path) else {
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
+ }
@@ -60,11 +60,37 @@ internal func isFileUrlPath(_ path: String) -> Bool {
60
60
  }
61
61
 
62
62
  internal func convertToUrl(string value: String) -> URL? {
63
- // URLComponents parses and constructs URLs according to RFC 3986.
64
- // For some unusual urls URL(string:) will fail incorrectly
65
- guard let url = URLComponents(string: value)?.url ?? URL(string: value) else {
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.16",
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": "0d1a3567cb0fce9c54e1185654be88bd0c7842d4"
47
+ "gitHead": "09b2d97bbc0f70f7c811ff9b6c9ad8808c5ad84b"
48
48
  }