expo 56.0.18 → 56.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.
@@ -10,7 +10,7 @@ buildscript {
10
10
  }
11
11
 
12
12
  group = 'host.exp.exponent'
13
- version = '56.0.18'
13
+ version = '56.0.20'
14
14
 
15
15
  expoModule {
16
16
  // We can't prebuild the module because it depends on the generated files.
@@ -21,7 +21,7 @@ android {
21
21
  namespace "expo.core"
22
22
  defaultConfig {
23
23
  versionCode 1
24
- versionName "56.0.18"
24
+ versionName "56.0.20"
25
25
  consumerProguardFiles("proguard-rules.pro")
26
26
  }
27
27
  testOptions {
@@ -1,10 +1,5 @@
1
1
  export declare class TextDecoder {
2
- private _encoding;
3
- private _ignoreBOM;
4
- private _errorMode;
5
- private _BOMseen;
6
- private _doNotFlush;
7
- private _decoder;
2
+ private readonly _state;
8
3
  constructor(label?: string, options?: {
9
4
  fatal?: boolean;
10
5
  ignoreBOM?: boolean;
@@ -12,9 +7,8 @@ export declare class TextDecoder {
12
7
  get encoding(): string;
13
8
  get fatal(): boolean;
14
9
  get ignoreBOM(): boolean;
15
- decode(input?: ArrayBuffer | DataView, options?: {
10
+ decode(input?: ArrayBuffer | ArrayBufferView, options?: {
16
11
  stream?: boolean;
17
12
  }): string;
18
- private serializeStream;
19
13
  }
20
14
  //# sourceMappingURL=TextDecoder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TextDecoder.d.ts","sourceRoot":"","sources":["../../src/winter/TextDecoder.ts"],"names":[],"mappings":"AAwTA,qBAAa,WAAW;IACtB,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,QAAQ,CAA4B;gBAG1C,KAAK,GAAE,MAAgB,EACvB,OAAO,GAAE;QACP,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,SAAS,CAAC,EAAE,OAAO,CAAC;KAChB;IAwBR,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,MAAM,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,QAAQ,EAAE,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,MAAM;IAmDlF,OAAO,CAAC,eAAe;CAcxB"}
1
+ {"version":3,"file":"TextDecoder.d.ts","sourceRoot":"","sources":["../../src/winter/TextDecoder.ts"],"names":[],"mappings":"AAqXA,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;gBAE9B,KAAK,GAAE,MAAgB,EAAE,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAO;IAgB3F,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,MAAM,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,eAAe,EAAE,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,MAAM;CAsB1F"}
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "@expo/dom-webview": "~56.0.6",
3
3
  "@expo/fingerprint": "~0.19.9",
4
- "@expo/metro-runtime": "~56.0.18",
4
+ "@expo/metro-runtime": "~56.0.20",
5
5
  "@expo/vector-icons": "^15.0.2",
6
- "@expo/ui": "~56.0.24",
6
+ "@expo/ui": "~56.0.26",
7
7
  "@react-native-async-storage/async-storage": "2.2.0",
8
8
  "@react-native-community/datetimepicker": "9.1.0",
9
9
  "@react-native-masked-view/masked-view": "0.3.2",
@@ -17,84 +17,84 @@
17
17
  "expo-analytics-amplitude": "~11.3.0",
18
18
  "expo-app-auth": "~11.1.0",
19
19
  "expo-app-loader-provider": "~8.0.0",
20
- "expo-app-metrics": "~56.0.23",
20
+ "expo-app-metrics": "~56.0.25",
21
21
  "expo-apple-authentication": "~56.0.4",
22
22
  "expo-application": "~56.0.3",
23
- "expo-asset": "~56.0.21",
23
+ "expo-asset": "~56.0.23",
24
24
  "expo-audio": "~56.0.13",
25
- "expo-auth-session": "~56.0.16",
26
- "expo-background-fetch": "~56.0.24",
27
- "expo-background-task": "~56.0.24",
25
+ "expo-auth-session": "~56.0.17",
26
+ "expo-background-fetch": "~56.0.26",
27
+ "expo-background-task": "~56.0.26",
28
28
  "expo-battery": "~56.0.4",
29
29
  "expo-blur": "~56.0.4",
30
30
  "expo-brightness": "~56.0.5",
31
- "expo-brownfield": "~56.0.26",
32
- "expo-build-properties": "~56.0.24",
33
- "expo-calendar": "~56.0.9",
31
+ "expo-brownfield": "~56.0.28",
32
+ "expo-build-properties": "~56.0.26",
33
+ "expo-calendar": "~56.0.10",
34
34
  "expo-camera": "~56.0.8",
35
35
  "expo-cellular": "~56.0.5",
36
36
  "expo-checkbox": "~56.0.1",
37
37
  "expo-clipboard": "~56.0.4",
38
- "expo-constants": "~56.0.22",
39
- "expo-contacts": "~56.0.12",
38
+ "expo-constants": "~56.0.24",
39
+ "expo-contacts": "~56.0.13",
40
40
  "expo-crypto": "~56.0.4",
41
- "expo-dev-client": "~56.0.24",
41
+ "expo-dev-client": "~56.0.25",
42
42
  "expo-device": "~56.0.4",
43
43
  "expo-document-picker": "~56.0.4",
44
- "expo-file-system": "~56.0.8",
44
+ "expo-file-system": "~56.0.10",
45
45
  "expo-font": "~56.0.7",
46
46
  "expo-gl": "~56.0.6",
47
47
  "expo-glass-effect": "~56.0.4",
48
48
  "expo-google-app-auth": "~8.3.0",
49
49
  "expo-haptics": "~56.0.3",
50
- "expo-image": "~56.0.11",
50
+ "expo-image": "~56.0.12",
51
51
  "expo-image-loader": "~56.0.3",
52
- "expo-image-manipulator": "~56.0.23",
53
- "expo-image-picker": "~56.0.22",
52
+ "expo-image-manipulator": "~56.0.25",
53
+ "expo-image-picker": "~56.0.24",
54
54
  "expo-intent-launcher": "~56.0.4",
55
- "expo-insights": "~56.0.22",
55
+ "expo-insights": "~56.0.24",
56
56
  "expo-keep-awake": "~56.0.3",
57
57
  "expo-linear-gradient": "~56.0.4",
58
- "expo-linking": "~56.0.16",
58
+ "expo-linking": "~56.0.17",
59
59
  "expo-local-authentication": "~56.0.5",
60
60
  "expo-localization": "~56.0.6",
61
- "expo-location": "~56.0.22",
61
+ "expo-location": "~56.0.24",
62
62
  "expo-mail-composer": "~56.0.4",
63
63
  "expo-manifests": "~56.0.4",
64
64
  "expo-maps": "~56.0.7",
65
65
  "expo-mcp": "~0.2.1",
66
- "expo-media-library": "~56.0.10",
66
+ "expo-media-library": "~56.0.11",
67
67
  "expo-mesh-gradient": "~56.0.3",
68
- "expo-module-template": "~56.0.17",
69
- "expo-modules-core": "~56.0.22",
68
+ "expo-module-template": "~56.0.18",
69
+ "expo-modules-core": "~56.0.24",
70
70
  "expo-navigation-bar": "~56.0.3",
71
71
  "expo-network": "~56.0.5",
72
- "expo-notifications": "~56.0.22",
73
- "expo-observe": "~56.0.26",
72
+ "expo-notifications": "~56.0.24",
73
+ "expo-observe": "~56.0.28",
74
74
  "expo-print": "~56.0.4",
75
75
  "expo-live-photo": "~56.0.4",
76
- "expo-router": "~56.2.17",
77
- "expo-screen-capture": "~56.0.4",
76
+ "expo-router": "~56.2.19",
77
+ "expo-screen-capture": "~56.0.5",
78
78
  "expo-screen-orientation": "~56.0.5",
79
79
  "expo-secure-store": "~56.0.4",
80
80
  "expo-sensors": "~56.0.6",
81
- "expo-server": "~56.0.5",
82
- "expo-sharing": "~56.0.23",
81
+ "expo-server": "~56.0.6",
82
+ "expo-sharing": "~56.0.25",
83
83
  "expo-sms": "~56.0.3",
84
84
  "expo-speech": "~56.0.3",
85
85
  "expo-splash-screen": "~56.0.14",
86
86
  "expo-sqlite": "~56.0.5",
87
87
  "expo-status-bar": "~56.0.4",
88
- "expo-store-review": "~56.0.3",
89
- "expo-symbols": "~56.0.6",
88
+ "expo-store-review": "~56.0.4",
89
+ "expo-symbols": "~56.0.7",
90
90
  "expo-system-ui": "~56.0.5",
91
- "expo-task-manager": "~56.0.24",
91
+ "expo-task-manager": "~56.0.26",
92
92
  "expo-tracking-transparency": "~56.0.5",
93
- "expo-updates": "~56.0.23",
93
+ "expo-updates": "~56.0.25",
94
94
  "expo-video-thumbnails": "~56.0.3",
95
95
  "expo-video": "~56.1.4",
96
96
  "expo-web-browser": "~56.0.6",
97
- "expo-widgets": "~56.0.25",
97
+ "expo-widgets": "~56.0.27",
98
98
  "jest-expo": "~56.0.5",
99
99
  "lottie-react-native": "~7.3.4",
100
100
  "react": "19.2.3",
@@ -0,0 +1,23 @@
1
+ // Copyright 2026-present 650 Industries. All rights reserved.
2
+
3
+ #pragma once
4
+
5
+ #import <ExpoModulesCore/Platform.h>
6
+ #import <Expo/RCTAppDelegateUmbrella.h>
7
+
8
+ NS_ASSUME_NONNULL_BEGIN
9
+
10
+ #if TARGET_OS_IOS || TARGET_OS_TV
11
+
12
+ NS_SWIFT_NAME(ExpoBundleConfiguration)
13
+ @interface EXBundleConfiguration : RCTBundleConfiguration
14
+
15
+ - (instancetype)initWithBundleURL:(NSURL *)bundleURL NS_DESIGNATED_INITIALIZER;
16
+
17
+ + (RCTBundleConfiguration *)configurationWithBundleURL:(nullable NSURL *)bundleURL NS_SWIFT_NAME(configuration(bundleURL:));
18
+
19
+ @end
20
+
21
+ #endif
22
+
23
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,85 @@
1
+ // Copyright 2026-present 650 Industries. All rights reserved.
2
+
3
+ #import <Expo/EXBundleConfiguration.h>
4
+
5
+ #if TARGET_OS_IOS || TARGET_OS_TV
6
+
7
+ @implementation EXBundleConfiguration {
8
+ NSURL *_bundleURL;
9
+ }
10
+
11
+ - (instancetype)initWithBundleURL:(NSURL *)bundleURL
12
+ {
13
+ if (bundleURL.fileURL) {
14
+ self = [super initWithBundleFilePath:bundleURL];
15
+ } else {
16
+ self = [super init];
17
+ }
18
+ if (self) {
19
+ _bundleURL = bundleURL;
20
+ }
21
+ return self;
22
+ }
23
+
24
+ - (NSURL *)getBundleURL
25
+ {
26
+ // A file URL is a snapshot of whichever bundle was current when this configuration was built,
27
+ // and `RCTBundleManager` prefers it over the live getter that `RCTHost` installs. Returning it
28
+ // would pin every reload to that bundle, so `Updates.reloadAsync` could never start a newly
29
+ // downloaded update.
30
+ if (_bundleURL.fileURL) {
31
+ return nil;
32
+ }
33
+
34
+ return _bundleURL;
35
+ }
36
+
37
+ - (NSString *)getPackagerServerScheme
38
+ {
39
+ if (_bundleURL.fileURL) {
40
+ return [super getPackagerServerScheme];
41
+ }
42
+
43
+ NSString *scheme = _bundleURL.scheme;
44
+ if ([scheme isEqualToString:@"https"] || [scheme isEqualToString:@"exps"]) {
45
+ return @"https";
46
+ } else if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"exp"]) {
47
+ return @"http";
48
+ } else {
49
+ return [super getPackagerServerScheme];
50
+ }
51
+ }
52
+
53
+ - (NSString *)getPackagerServerHost
54
+ {
55
+ if (_bundleURL.fileURL) {
56
+ return [super getPackagerServerHost];
57
+ }
58
+
59
+ NSString *host = _bundleURL.host;
60
+ if (host == nil) {
61
+ return [super getPackagerServerHost];
62
+ }
63
+
64
+ NSNumber *port = _bundleURL.port;
65
+ if (port == nil) {
66
+ return host;
67
+ } else if ([host containsString:@":"] && ![host hasPrefix:@"["]) {
68
+ return [NSString stringWithFormat:@"[%@]:%@", host, port];
69
+ } else {
70
+ return [NSString stringWithFormat:@"%@:%@", host, port];
71
+ }
72
+ }
73
+
74
+ + (RCTBundleConfiguration *)configurationWithBundleURL:(nullable NSURL *)bundleURL
75
+ {
76
+ if (bundleURL == nil) {
77
+ return [RCTBundleConfiguration defaultConfiguration];
78
+ } else {
79
+ return [[EXBundleConfiguration alloc] initWithBundleURL:bundleURL];
80
+ }
81
+ }
82
+
83
+ @end
84
+
85
+ #endif
@@ -26,6 +26,47 @@ public class ExpoReactNativeFactory: ExpoReactNativeFactoryObjC, ExpoReactNative
26
26
  super.init(delegate: delegate, releaseLevel: releaseLevel)
27
27
  }
28
28
 
29
+ // `RCTBundleConfiguration` is only available in react-native 0.84+, so it doesn't exist yet on react-native-macos.
30
+ #if os(iOS) || os(tvOS)
31
+ private var _bundleConfiguration: RCTBundleConfiguration?
32
+
33
+ public override var bundleConfiguration: RCTBundleConfiguration {
34
+ get {
35
+ if let _bundleConfiguration {
36
+ return _bundleConfiguration
37
+ }
38
+ adoptInfoPlistMetroPort()
39
+ return ExpoBundleConfiguration.configuration(bundleURL: self.delegate?.bundleURL())
40
+ }
41
+ set {
42
+ _bundleConfiguration = newValue
43
+ }
44
+ }
45
+
46
+ private func adoptInfoPlistMetroPort() {
47
+ #if DEBUG
48
+ if NSClassFromString("EXDevLauncherController") != nil {
49
+ return
50
+ }
51
+ guard let port = Bundle.main.object(forInfoDictionaryKey: "RCTMetroPort") as? String,
52
+ !port.isEmpty else {
53
+ return
54
+ }
55
+ var host = "localhost"
56
+ if let ipPath = Bundle.main.path(forResource: "ip", ofType: "txt"),
57
+ let ip = try? String(contentsOfFile: ipPath, encoding: .utf8).trimmingCharacters(in: .whitespacesAndNewlines),
58
+ !ip.isEmpty {
59
+ host = ip
60
+ }
61
+ let settings = RCTBundleURLProvider.sharedSettings()
62
+ let target = "\(host):\(port)"
63
+ if settings.jsLocation != target {
64
+ settings.jsLocation = target
65
+ }
66
+ #endif
67
+ }
68
+ #endif
69
+
29
70
  @MainActor
30
71
  @objc func createRCTRootViewFactory() -> RCTRootViewFactory {
31
72
  // Alan: This is temporary. We need to cast to ExpoReactNativeFactoryDelegate here because currently, if you extend RCTReactNativeFactory
@@ -108,17 +149,25 @@ public class ExpoReactNativeFactory: ExpoReactNativeFactoryObjC, ExpoReactNative
108
149
  }
109
150
  }
110
151
 
152
+ #if os(iOS) || os(tvOS)
153
+ adoptInfoPlistMetroPort()
154
+ #endif
155
+
111
156
  let rootView: UIView
112
157
  if let factory = self.rootViewFactory as? ExpoReactRootViewFactory {
113
158
  // RCTDevMenuConfiguration is only available in react-native 0.83+
159
+ // bundleConfiguration is only accepted in react-native 0.84+
114
160
  #if os(iOS) || os(tvOS)
161
+ let bundleConfiguration = ExpoBundleConfiguration.configuration(
162
+ bundleURL: withBundleURL ?? configuration?.bundleURLBlock()
163
+ )
115
164
  // When calling `recreateRootViewWithBundleURL:` from `EXReactRootViewFactory`,
116
165
  // we don't want to loop the ReactDelegate again. Otherwise, it will be an infinite loop.
117
166
  rootView = factory.superView(
118
167
  withModuleName: moduleName ?? defaultModuleName,
119
168
  initialProperties: initialProps,
120
169
  launchOptions: launchOptions ?? [:],
121
- bundleConfiguration: RCTBundleConfiguration.default(),
170
+ bundleConfiguration: bundleConfiguration,
122
171
  devMenuConfiguration: self.devMenuConfiguration
123
172
  )
124
173
  #else
@@ -130,11 +179,14 @@ public class ExpoReactNativeFactory: ExpoReactNativeFactoryObjC, ExpoReactNative
130
179
  #endif
131
180
  } else {
132
181
  #if os(iOS) || os(tvOS)
182
+ let bundleConfiguration = ExpoBundleConfiguration.configuration(
183
+ bundleURL: withBundleURL ?? configuration?.bundleURLBlock()
184
+ )
133
185
  rootView = rootViewFactory.view(
134
186
  withModuleName: moduleName ?? defaultModuleName,
135
187
  initialProperties: initialProps,
136
188
  launchOptions: launchOptions,
137
- bundleConfiguration: RCTBundleConfiguration.default(),
189
+ bundleConfiguration: bundleConfiguration,
138
190
  devMenuConfiguration: self.devMenuConfiguration ?? RCTDevMenuConfiguration.default()
139
191
  )
140
192
  #else
package/ios/Expo.h CHANGED
@@ -1,4 +1,5 @@
1
1
  #import <ExpoModulesCore/ExpoModulesCore.h>
2
2
  #import <Expo/EXAppDefinesLoader.h>
3
3
  #import <Expo/ExpoReactNativeFactory.h>
4
+ #import <Expo/EXBundleConfiguration.h>
4
5
  #import <Expo/RCTAppDelegateUmbrella.h>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo",
3
- "version": "56.0.18",
3
+ "version": "56.0.20",
4
4
  "description": "The Expo SDK",
5
5
  "main": "src/Expo.ts",
6
6
  "module": "src/Expo.ts",
@@ -68,9 +68,9 @@
68
68
  "homepage": "https://github.com/expo/expo/tree/main/packages/expo",
69
69
  "dependencies": {
70
70
  "@babel/runtime": "^7.20.0",
71
- "@expo/cli": "^56.1.22",
71
+ "@expo/cli": "^56.1.24",
72
72
  "@expo/config": "~56.0.13",
73
- "@expo/config-plugins": "~56.0.14",
73
+ "@expo/config-plugins": "~56.0.15",
74
74
  "@expo/devtools": "~56.0.2",
75
75
  "@expo/dom-webview": "~56.0.6",
76
76
  "@expo/fingerprint": "^0.19.9",
@@ -79,14 +79,14 @@
79
79
  "@expo/metro": "~56.0.0",
80
80
  "@expo/metro-config": "~56.0.18",
81
81
  "@ungap/structured-clone": "^1.3.0",
82
- "babel-preset-expo": "~56.0.18",
83
- "expo-asset": "~56.0.21",
84
- "expo-constants": "~56.0.22",
85
- "expo-file-system": "~56.0.8",
82
+ "babel-preset-expo": "~56.0.19",
83
+ "expo-asset": "~56.0.23",
84
+ "expo-constants": "~56.0.24",
85
+ "expo-file-system": "~56.0.10",
86
86
  "expo-font": "~56.0.7",
87
87
  "expo-keep-awake": "~56.0.3",
88
- "expo-modules-autolinking": "~56.0.21",
89
- "expo-modules-core": "~56.0.22",
88
+ "expo-modules-autolinking": "~56.0.22",
89
+ "expo-modules-core": "~56.0.24",
90
90
  "pretty-format": "^29.7.0",
91
91
  "react-refresh": "^0.14.2",
92
92
  "whatwg-url-minimum": "^0.1.2"
@@ -100,9 +100,9 @@
100
100
  "react-native": "0.85.3",
101
101
  "rimraf": "^6.1.2",
102
102
  "web-streams-polyfill": "^3.3.2",
103
+ "expo-updates": "56.0.25",
103
104
  "@expo/dom-webview": "56.0.6",
104
- "expo-updates": "56.0.23",
105
- "@expo/metro-runtime": "56.0.18"
105
+ "@expo/metro-runtime": "56.0.20"
106
106
  },
107
107
  "peerDependencies": {
108
108
  "@expo/dom-webview": "*",
@@ -139,7 +139,7 @@
139
139
  "scripts/resolveAppEntry.js"
140
140
  ]
141
141
  },
142
- "gitHead": "006eb2b47c82febc52fc87d61a1e235bf804dd79",
142
+ "gitHead": "39f9dc47fb5ddd099a2533dd0ff24be7126a1ed0",
143
143
  "scripts": {
144
144
  "build": "tsc",
145
145
  "clean": "rimraf build",
@@ -90,20 +90,9 @@ describe('asyncRequireModule', () => {
90
90
 
91
91
  function asyncRequire(moduleID, paths, moduleName) {
92
92
  var ret = asyncRequireImpl(moduleID, paths, moduleName);
93
- if (!(ret instanceof Promise)) {
94
- return {
95
- _result: ret,
96
- then: function(resolve, reject) {
97
- return Promise.resolve(ret).then(resolve, reject);
98
- },
99
- };
100
- }
101
- return {
102
- _result: ret,
103
- then: function(resolve, reject) {
104
- return ret.then(resolve, reject);
105
- },
106
- };
93
+ var promise = Promise.resolve(ret);
94
+ promise._result = ret;
95
+ return promise;
107
96
  }
108
97
 
109
98
  asyncRequire.unstable_importMaybeSync = function(moduleID, paths) {
@@ -225,14 +214,20 @@ describe('asyncRequireModule', () => {
225
214
  });
226
215
 
227
216
  describe('thenable return value', () => {
228
- it('exposes a synchronous _result when no bundle load was needed', () => {
217
+ it('returns a Promise with a synchronous _result when no bundle load was needed', async () => {
229
218
  const ret = asyncRequire(42, null, 'my-module');
230
219
 
231
- expect(typeof ret.then).toBe('function');
220
+ const onFinally = jest.fn();
221
+
222
+ expect(ret).toBeInstanceOf(Promise);
223
+ expect(typeof ret.catch).toBe('function');
224
+ expect(typeof ret.finally).toBe('function');
232
225
  expect(ret._result).toEqual({ default: 'module-42' });
226
+ await expect(ret.finally(onFinally)).resolves.toEqual({ default: 'module-42' });
227
+ expect(onFinally).toHaveBeenCalledTimes(1);
233
228
  });
234
229
 
235
- it('exposes a Promise _result when a bundle load was needed', async () => {
230
+ it('returns a Promise with a Promise _result when a bundle load was needed', async () => {
236
231
  process.env.EXPO_OS = 'web';
237
232
  mockImportAll
238
233
  .mockImplementationOnce(() => {
@@ -244,10 +239,22 @@ describe('asyncRequireModule', () => {
244
239
 
245
240
  const ret = asyncRequire(42, { '42': '/bundles/my-module.bundle' }, 'my-module');
246
241
 
247
- expect(typeof ret.then).toBe('function');
242
+ expect(ret).toBeInstanceOf(Promise);
243
+ expect(typeof ret.catch).toBe('function');
244
+ expect(typeof ret.finally).toBe('function');
248
245
  expect(ret._result).toBeInstanceOf(Promise);
249
246
  await expect(ret._result).resolves.toEqual({ default: 'module-42' });
250
247
  });
248
+
249
+ it('supports catching a rejected bundle load', async () => {
250
+ process.env.EXPO_OS = 'ios';
251
+ const error = new Error('Bundle load failed');
252
+ (globalThis as any).__loadBundleAsync = jest.fn(() => Promise.reject(error));
253
+
254
+ const ret = asyncRequire(42, { '42': '/bundles/my-module.bundle' }, 'my-module');
255
+
256
+ await expect(ret.catch((caughtError: Error) => caughtError)).resolves.toBe(error);
257
+ });
251
258
  });
252
259
 
253
260
  describe('unstable_importMaybeSync', () => {
@@ -94,30 +94,22 @@ function asyncRequireImpl<T>(
94
94
  return importAll();
95
95
  }
96
96
 
97
+ type PromiseWithResult<T> = Promise<T> & {
98
+ _result?: T | Promise<T>;
99
+ };
100
+
97
101
  function asyncRequire<T>(
98
102
  moduleID: number,
99
103
  paths: DependencyMapPaths,
100
104
  moduleName?: string
101
- ): PromiseLike<T> & { _result?: T | Promise<T> } {
105
+ ): PromiseWithResult<T> {
102
106
  const ret = asyncRequireImpl<T>(moduleID, paths, moduleName);
103
- if (!(ret instanceof Promise)) {
104
- // We return a thenable with an added `unstable_importMaybeSync`-like
105
- // `_result` property to bypass this being force-converted to a promise
106
- // for rehydration
107
- return {
108
- _result: ret,
109
- then(resolve, reject) {
110
- return Promise.resolve(ret).then(resolve, reject);
111
- },
112
- };
113
- } else {
114
- return {
115
- _result: ret,
116
- then(resolve, reject) {
117
- return ret.then(resolve, reject);
118
- },
119
- };
120
- }
107
+ // We return a Promise with an added `unstable_importMaybeSync`-like
108
+ // `_result` property to bypass this being force-converted to a promise
109
+ // for rehydration
110
+ const promise: PromiseWithResult<T> = Promise.resolve(ret);
111
+ promise._result = ret;
112
+ return promise;
121
113
  }
122
114
 
123
115
  // Synchronous version of asyncRequire, which can still return a promise