expo-modules-core 3.0.19 → 3.0.20

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,13 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 3.0.20 — 2025-10-01
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - [iOS] Fix NSURL to JSIString conversion returning nil. ([#39567](https://github.com/expo/expo/pull/39567) by [@behenate](https://github.com/behenate))
18
+ - Fix SharedObject created with `useReleasingSharedObject` getting destroyed after a fast refresh caused by a change in its dependencies. ([#39753](https://github.com/expo/expo/pull/39753) by [@behenate](https://github.com/behenate))
19
+
13
20
  ## 3.0.19 — 2025-10-01
14
21
 
15
22
  ### 💡 Others
@@ -29,7 +29,7 @@ if (shouldIncludeCompose) {
29
29
  }
30
30
 
31
31
  group = 'host.exp.exponent'
32
- version = '3.0.19'
32
+ version = '3.0.20'
33
33
 
34
34
  def isExpoModulesCoreTests = {
35
35
  Gradle gradle = getGradle()
@@ -79,7 +79,7 @@ android {
79
79
  defaultConfig {
80
80
  consumerProguardFiles 'proguard-rules.pro'
81
81
  versionCode 1
82
- versionName "3.0.19"
82
+ versionName "3.0.20"
83
83
  buildConfigField "String", "EXPO_MODULES_CORE_VERSION", "\"${versionName}\""
84
84
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled.toString()
85
85
 
@@ -1 +1 @@
1
- {"version":3,"file":"useReleasingSharedObject.d.ts","sourceRoot":"","sources":["../../src/hooks/useReleasingSharedObject.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAA8B,MAAM,OAAO,CAAC;AAEnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAEpE;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,aAAa,SAAS,YAAY,EACzE,OAAO,EAAE,MAAM,aAAa,EAC5B,YAAY,EAAE,cAAc,GAC3B,aAAa,CAwCf"}
1
+ {"version":3,"file":"useReleasingSharedObject.d.ts","sourceRoot":"","sources":["../../src/hooks/useReleasingSharedObject.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAA8B,MAAM,OAAO,CAAC;AAEnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAEpE;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,aAAa,SAAS,YAAY,EACzE,OAAO,EAAE,MAAM,aAAa,EAC5B,YAAY,EAAE,cAAc,GAC3B,aAAa,CA0Cf"}
@@ -24,6 +24,8 @@ namespace expo
24
24
 
25
25
  jsi::String convertNSStringToJSIString(jsi::Runtime &runtime, NSString *value);
26
26
 
27
+ jsi::String convertNSURLToJSIString(jsi::Runtime &runtime, NSURL *value);
28
+
27
29
  jsi::Object convertNSDictionaryToJSIObject(jsi::Runtime &runtime, NSDictionary *value);
28
30
 
29
31
  jsi::Array convertNSArrayToJSIArray(jsi::Runtime &runtime, NSArray *value);
@@ -9,6 +9,7 @@
9
9
  #import <ExpoModulesCore/EXJavaScriptRuntime.h>
10
10
  #import <ExpoModulesCore/EXJavaScriptSharedObjectBinding.h>
11
11
  #import <ExpoModulesCore/EXStringUtils.h>
12
+ #import <Foundation/NSURL.h>
12
13
 
13
14
  namespace expo {
14
15
 
@@ -41,6 +42,12 @@ jsi::String convertNSStringToJSIString(jsi::Runtime &runtime, NSString *value)
41
42
  #endif
42
43
  }
43
44
 
45
+ jsi::String convertNSURLToJSIString(jsi::Runtime &runtime, NSURL *value)
46
+ {
47
+ NSString *stringValue = [value absoluteString];
48
+ return convertNSStringToJSIString(runtime, stringValue);
49
+ }
50
+
44
51
  jsi::Object convertNSDictionaryToJSIObject(jsi::Runtime &runtime, NSDictionary *value)
45
52
  {
46
53
  jsi::Object result = jsi::Object(runtime);
@@ -107,6 +114,8 @@ jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value)
107
114
  return convertNSArrayToJSIArray(runtime, (NSArray *)value);
108
115
  } else if ([value isKindOfClass:[NSData class]]) {
109
116
  return createUint8Array(runtime, (NSData *)value);
117
+ } else if ([value isKindOfClass:[NSURL class]]) {
118
+ return convertNSURLToJSIString(runtime, (NSURL *)value);
110
119
  } else if (value == (id)kCFNull) {
111
120
  return jsi::Value::null();
112
121
  }
@@ -245,6 +245,17 @@ class FunctionSpec: ExpoSpec {
245
245
  @Field var property: String = "expo"
246
246
  }
247
247
 
248
+ struct TestURLRecord: Record {
249
+ static let defaultURLString = "https://expo.dev"
250
+ static let defaultURL = URL(string: defaultURLString)!
251
+
252
+ @Field var url: URL = defaultURL
253
+ }
254
+
255
+ afterEach {
256
+ try runtime.eval("globalThis.result = undefined")
257
+ }
258
+
248
259
  beforeSuite {
249
260
  appContext.moduleRegistry.register(holder: mockModuleHolder(appContext) {
250
261
  Name("TestModule")
@@ -286,10 +297,18 @@ class FunctionSpec: ExpoSpec {
286
297
  return "\(f.property)"
287
298
  }
288
299
 
300
+ Function("withURL") {
301
+ return TestURLRecord.defaultURL
302
+ }
303
+
304
+ Function("withNestedURL") {
305
+ return TestURLRecord()
306
+ }
307
+
289
308
  Function("withOptionalRecord") { (f: TestRecord?) in
290
309
  return "\(f?.property ?? "no value")"
291
310
  }
292
-
311
+
293
312
  Function("withSharedObject") {
294
313
  return SharedString("Test")
295
314
  }
@@ -305,7 +324,15 @@ class FunctionSpec: ExpoSpec {
305
324
  AsyncFunction("withSharedObjectPromise") { (p: Promise) in
306
325
  p.resolve(SharedString("Test with Promise"))
307
326
  }
308
-
327
+
328
+ AsyncFunction("withURLAsync") {
329
+ return TestURLRecord.defaultURL
330
+ }
331
+
332
+ AsyncFunction("withNestedURLAsync") {
333
+ return TestURLRecord()
334
+ }
335
+
309
336
  Class("Shared", SharedString.self) {
310
337
  Property("value") { shared in
311
338
  return shared.ref
@@ -360,7 +387,43 @@ class FunctionSpec: ExpoSpec {
360
387
  it("accepts optional record") {
361
388
  expect(try runtime.eval("expo.modules.TestModule.withOptionalRecord({property: \"123\"})").asString()) == "123"
362
389
  }
363
-
390
+
391
+ it("returns URL (sync)") {
392
+ let result = try runtime.eval("globalThis.result = expo.modules.TestModule.withURL()")
393
+ expect(result.kind) == .string
394
+ expect(result.getString()) == TestURLRecord.defaultURLString
395
+ }
396
+
397
+ it("returns URL (async)") {
398
+ try runtime.eval("expo.modules.TestModule.withURLAsync().then((result) => { globalThis.result = result; })")
399
+ expect(safeBoolEval("!!globalThis.result")).toEventually(beTrue(), timeout: .milliseconds(2000))
400
+
401
+ let urlValue = try runtime.eval("url = globalThis.result")
402
+ expect(urlValue.kind) == .string
403
+ expect(urlValue.getString()) == TestURLRecord.defaultURLString
404
+ }
405
+
406
+ it("returns a record wit url (sync)") {
407
+ let object = try runtime.eval("globalThis.result = expo.modules.TestModule.withNestedURL()")
408
+ expect(object.kind) == .object
409
+ expect(object.getObject().hasProperty("url")) == true
410
+ expect(object.getObject().getProperty("url").getString()) == TestURLRecord.defaultURLString
411
+ }
412
+
413
+ it("returns a record with url (async)") {
414
+ try runtime.eval("expo.modules.TestModule.withNestedURLAsync().then((result) => { globalThis.result = result; })")
415
+
416
+ expect(safeBoolEval("!!globalThis.result.url")).toEventually(beTrue(), timeout: .milliseconds(2000))
417
+
418
+ let object = try runtime.eval("object = globalThis.result")
419
+ expect(object.kind) == .object
420
+ expect(object.getObject().hasProperty("url")) == true
421
+
422
+ let urlValue = try runtime.eval("object.url")
423
+ expect(urlValue.kind) == .string
424
+ expect(urlValue.getString()) == TestURLRecord.defaultURLString
425
+ }
426
+
364
427
  it("returns a SharedObject (sync)") {
365
428
  let object = try runtime.eval("expo.modules.TestModule.withSharedObject()")
366
429
 
@@ -385,7 +448,7 @@ class FunctionSpec: ExpoSpec {
385
448
  expect(result.kind) == .string
386
449
  expect(result.getString()) == "Test"
387
450
  }
388
-
451
+
389
452
  it("returns an Array of SharedObjects (async)") {
390
453
  try runtime
391
454
  .eval(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-modules-core",
3
- "version": "3.0.19",
3
+ "version": "3.0.20",
4
4
  "description": "The core of Expo Modules architecture",
5
5
  "main": "src/index.ts",
6
6
  "types": "build/index.d.ts",
@@ -65,5 +65,5 @@
65
65
  "@testing-library/react-native": "^13.2.0",
66
66
  "expo-module-scripts": "^5.0.7"
67
67
  },
68
- "gitHead": "1ab3b9621b78b77b81049ebf06149753a1e0898c"
68
+ "gitHead": "73d9bc0ee4f1e28cfda349dc36ee53d16fad0c5d"
69
69
  }
@@ -26,18 +26,20 @@ export function useReleasingSharedObject<TSharedObject extends SharedObject>(
26
26
  dependencies.every((value, index) => value === previousDependencies.current[index]);
27
27
 
28
28
  // If the dependencies have changed, release the previous object and create a new one, otherwise this has been called
29
- // because of a fast refresh, and we don't want to release the object.
29
+ // because of an unrelated fast refresh, and we don't want to release the object.
30
30
  if (!newObject || !dependenciesAreEqual) {
31
31
  objectRef.current?.release();
32
32
  newObject = factory();
33
33
  objectRef.current = newObject;
34
34
  previousDependencies.current = dependencies;
35
- } else {
36
- isFastRefresh.current = true;
37
35
  }
38
36
  return newObject;
39
37
  }, dependencies);
40
38
 
39
+ useMemo(() => {
40
+ isFastRefresh.current = true;
41
+ }, []);
42
+
41
43
  useEffect(() => {
42
44
  isFastRefresh.current = false;
43
45