expo 57.0.9 → 57.0.11
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/android/build.gradle +2 -2
- package/build/winter/fetch/FetchResponse.d.ts +17 -0
- package/build/winter/fetch/FetchResponse.d.ts.map +1 -1
- package/build/winter/fetch/fetch.d.ts.map +1 -1
- package/bundledNativeModules.json +26 -26
- package/ios/Fetch/NativeResponse.swift +59 -26
- package/package.json +10 -10
- package/src/async-require/__tests__/asyncRequireModule.test.ts +25 -18
- package/src/async-require/asyncRequireModule.ts +11 -19
- package/src/winter/fetch/FetchResponse.ts +104 -27
- package/src/winter/fetch/__tests__/FetchResponse-test.ts +90 -0
- package/src/winter/fetch/fetch.ts +4 -1
package/android/build.gradle
CHANGED
|
@@ -10,7 +10,7 @@ buildscript {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
group = 'host.exp.exponent'
|
|
13
|
-
version = '57.0.
|
|
13
|
+
version = '57.0.11'
|
|
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 "57.0.
|
|
24
|
+
versionName "57.0.11"
|
|
25
25
|
consumerProguardFiles("proguard-rules.pro")
|
|
26
26
|
}
|
|
27
27
|
testOptions {
|
|
@@ -10,7 +10,24 @@ declare const stateKey: unique symbol;
|
|
|
10
10
|
export declare class FetchResponse extends ConcreteNativeResponse implements Response {
|
|
11
11
|
private readonly abortCleanupFunction;
|
|
12
12
|
private [stateKey];
|
|
13
|
+
private bodyStreamClosed;
|
|
14
|
+
private bodyStreamController;
|
|
15
|
+
private abortReason;
|
|
13
16
|
constructor(abortCleanupFunction: AbortSubscriptionCleanupFunction);
|
|
17
|
+
/**
|
|
18
|
+
* Rejects the pending body read with the abort reason and closes the
|
|
19
|
+
* teardown guard so late native events are dropped (#34804). Idempotent,
|
|
20
|
+
* and safe to call before the body stream exists.
|
|
21
|
+
*/
|
|
22
|
+
abort(reason?: unknown): void;
|
|
23
|
+
/**
|
|
24
|
+
* These helpers run on the native emitter's stack, where a throw reaches the
|
|
25
|
+
* global handler. The guard can be stale (internal stream errors bypass
|
|
26
|
+
* `controller.error()`), so check `desiredSize` (null = errored) and catch.
|
|
27
|
+
*/
|
|
28
|
+
private closeBodyStream;
|
|
29
|
+
private errorBodyStream;
|
|
30
|
+
private enqueueBodyChunk;
|
|
14
31
|
get _rawHeaders(): NativeHeadersType;
|
|
15
32
|
get status(): number;
|
|
16
33
|
get statusText(): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FetchResponse.d.ts","sourceRoot":"","sources":["../../../src/winter/fetch/FetchResponse.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGzE,QAAA,MAAM,sBAAsB,EAAqC,OAAO,cAAc,CAAC;AACvF,MAAM,MAAM,gCAAgC,GAAG,MAAM,IAAI,CAAC;AAI1D,KAAK,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AACvE,KAAK,iBAAiB,GAAG,UAAU,CAAC,QAAQ,GAAG,UAAU,CAAC;AAW1D,QAAA,MAAM,QAAQ,eAAgC,CAAC;AAuI/C;;GAEG;AACH,qBAAa,aAAc,SAAQ,sBAAuB,YAAW,QAAQ;
|
|
1
|
+
{"version":3,"file":"FetchResponse.d.ts","sourceRoot":"","sources":["../../../src/winter/fetch/FetchResponse.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGzE,QAAA,MAAM,sBAAsB,EAAqC,OAAO,cAAc,CAAC;AACvF,MAAM,MAAM,gCAAgC,GAAG,MAAM,IAAI,CAAC;AAI1D,KAAK,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AACvE,KAAK,iBAAiB,GAAG,UAAU,CAAC,QAAQ,GAAG,UAAU,CAAC;AAW1D,QAAA,MAAM,QAAQ,eAAgC,CAAC;AAuI/C;;GAEG;AACH,qBAAa,aAAc,SAAQ,sBAAuB,YAAW,QAAQ;IAa/D,OAAO,CAAC,QAAQ,CAAC,oBAAoB;IAZjD,OAAO,CAAC,CAAC,QAAQ,CAAC,CAGhB;IAIF,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,oBAAoB,CACrB;IACP,OAAO,CAAC,WAAW,CAAsB;gBAEZ,oBAAoB,EAAE,gCAAgC;IAKnF;;;;OAIG;IACH,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI;IAmB7B;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,gBAAgB;IAkBxB,IAAa,WAAW,IAAI,iBAAiB,CAE5C;IAED,IAAa,MAAM,IAAI,MAAM,CAE5B;IAED,IAAa,UAAU,IAAI,MAAM,CAEhC;IAED,IAAa,GAAG,IAAI,MAAM,CAEzB;IAED,IAAa,UAAU,IAAI,OAAO,CAEjC;IAED,IAAI,IAAI,IAAI,SAAS,CAEpB;IAED,IAAI,IAAI,IAAI,cAAc,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,GAAG,IAAI,CAoEzD;IAED,IAAa,QAAQ,IAAI,OAAO,CAE/B;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,EAAE,IAAI,OAAO,CAEhB;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBrB,QAAQ,IAAI,OAAO,CAAC,iBAAiB,CAAC;IAgBtC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC;IAMpB,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IAKhC,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC;IAanC,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IAatC,QAAQ,IAAI,MAAM;IAIlB,MAAM,IAAI,MAAM;IAShB,KAAK,IAAI,aAAa;IAgDtB,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,QAAQ,CAQd;CACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../src/winter/fetch/fetch.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAyC,MAAM,iBAAiB,CAAC;AAQvF,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAwBxE,wBAAsB,KAAK,CACzB,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,gBAAgB,EACtC,IAAI,CAAC,EAAE,gBAAgB,GACtB,OAAO,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../src/winter/fetch/fetch.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAyC,MAAM,iBAAiB,CAAC;AAQvF,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAwBxE,wBAAsB,KAAK,CACzB,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,gBAAgB,EACtC,IAAI,CAAC,EAAE,gBAAgB,GACtB,OAAO,CAAC,aAAa,CAAC,CA0DxB"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"@expo/fingerprint": "~0.20.6",
|
|
4
4
|
"@expo/metro-runtime": "~57.0.8",
|
|
5
5
|
"@expo/vector-icons": "^15.0.2",
|
|
6
|
-
"@expo/ui": "~57.0.
|
|
6
|
+
"@expo/ui": "~57.0.9",
|
|
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,85 +17,85 @@
|
|
|
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": "~57.0.
|
|
20
|
+
"expo-app-metrics": "~57.0.9",
|
|
21
21
|
"expo-apple-authentication": "~57.0.1",
|
|
22
22
|
"expo-application": "~57.0.2",
|
|
23
|
-
"expo-asset": "~57.0.
|
|
23
|
+
"expo-asset": "~57.0.9",
|
|
24
24
|
"expo-audio": "~57.0.3",
|
|
25
|
-
"expo-auth-session": "~57.0.
|
|
26
|
-
"expo-background-fetch": "~57.0.
|
|
27
|
-
"expo-background-task": "~57.0.
|
|
25
|
+
"expo-auth-session": "~57.0.6",
|
|
26
|
+
"expo-background-fetch": "~57.0.8",
|
|
27
|
+
"expo-background-task": "~57.0.8",
|
|
28
28
|
"expo-battery": "~57.0.1",
|
|
29
29
|
"expo-blur": "~57.0.2",
|
|
30
30
|
"expo-brightness": "~57.0.1",
|
|
31
|
-
"expo-brownfield": "~57.0.
|
|
32
|
-
"expo-build-properties": "~57.0.
|
|
31
|
+
"expo-brownfield": "~57.0.9",
|
|
32
|
+
"expo-build-properties": "~57.0.9",
|
|
33
33
|
"expo-calendar": "~57.0.1",
|
|
34
34
|
"expo-camera": "~57.0.3",
|
|
35
35
|
"expo-cellular": "~57.0.1",
|
|
36
36
|
"expo-checkbox": "~57.0.0",
|
|
37
37
|
"expo-clipboard": "~57.0.1",
|
|
38
|
-
"expo-constants": "~57.0.
|
|
38
|
+
"expo-constants": "~57.0.9",
|
|
39
39
|
"expo-contacts": "~57.0.3",
|
|
40
40
|
"expo-crypto": "~57.0.1",
|
|
41
41
|
"expo-dev-client": "~57.0.10",
|
|
42
42
|
"expo-device": "~57.0.1",
|
|
43
43
|
"expo-document-picker": "~57.0.1",
|
|
44
44
|
"expo-eas-client": "~57.0.1",
|
|
45
|
-
"expo-file-system": "~57.0.
|
|
45
|
+
"expo-file-system": "~57.0.2",
|
|
46
46
|
"expo-font": "~57.0.1",
|
|
47
47
|
"expo-gl": "~57.0.2",
|
|
48
48
|
"expo-glass-effect": "~57.0.1",
|
|
49
49
|
"expo-google-app-auth": "~8.3.0",
|
|
50
50
|
"expo-haptics": "~57.0.1",
|
|
51
|
-
"expo-image": "~57.0.
|
|
51
|
+
"expo-image": "~57.0.2",
|
|
52
52
|
"expo-image-loader": "~57.0.1",
|
|
53
|
-
"expo-image-manipulator": "~57.0.
|
|
54
|
-
"expo-image-picker": "~57.0.
|
|
53
|
+
"expo-image-manipulator": "~57.0.8",
|
|
54
|
+
"expo-image-picker": "~57.0.8",
|
|
55
55
|
"expo-intent-launcher": "~57.0.1",
|
|
56
|
-
"expo-insights": "~57.0.
|
|
56
|
+
"expo-insights": "~57.0.8",
|
|
57
57
|
"expo-keep-awake": "~57.0.1",
|
|
58
58
|
"expo-linear-gradient": "~57.0.1",
|
|
59
|
-
"expo-linking": "~57.0.
|
|
59
|
+
"expo-linking": "~57.0.5",
|
|
60
60
|
"expo-local-authentication": "~57.0.2",
|
|
61
61
|
"expo-localization": "~57.0.1",
|
|
62
|
-
"expo-location": "~57.0.
|
|
62
|
+
"expo-location": "~57.0.8",
|
|
63
63
|
"expo-mail-composer": "~57.0.1",
|
|
64
64
|
"expo-manifests": "~57.0.1",
|
|
65
65
|
"expo-maps": "~57.0.1",
|
|
66
66
|
"expo-mcp": "~0.2.1",
|
|
67
67
|
"expo-media-library": "~57.0.3",
|
|
68
68
|
"expo-mesh-gradient": "~57.0.1",
|
|
69
|
-
"expo-module-template": "~57.0.
|
|
70
|
-
"expo-modules-core": "~57.0.
|
|
69
|
+
"expo-module-template": "~57.0.6",
|
|
70
|
+
"expo-modules-core": "~57.0.10",
|
|
71
71
|
"expo-navigation-bar": "~57.0.2",
|
|
72
72
|
"expo-network": "~57.0.1",
|
|
73
|
-
"expo-notifications": "~57.0.
|
|
74
|
-
"expo-observe": "~57.0.
|
|
73
|
+
"expo-notifications": "~57.0.9",
|
|
74
|
+
"expo-observe": "~57.0.11",
|
|
75
75
|
"expo-print": "~57.0.1",
|
|
76
76
|
"expo-live-photo": "~57.0.1",
|
|
77
|
-
"expo-router": "~57.0.
|
|
77
|
+
"expo-router": "~57.0.11",
|
|
78
78
|
"expo-screen-capture": "~57.0.1",
|
|
79
79
|
"expo-screen-orientation": "~57.0.1",
|
|
80
80
|
"expo-secure-store": "~57.0.1",
|
|
81
81
|
"expo-sensors": "~57.0.2",
|
|
82
82
|
"expo-server": "~57.0.1",
|
|
83
|
-
"expo-sharing": "~57.0.
|
|
83
|
+
"expo-sharing": "~57.0.9",
|
|
84
84
|
"expo-sms": "~57.0.1",
|
|
85
85
|
"expo-speech": "~57.0.1",
|
|
86
86
|
"expo-splash-screen": "~57.0.5",
|
|
87
87
|
"expo-sqlite": "~57.0.1",
|
|
88
88
|
"expo-status-bar": "~57.0.1",
|
|
89
89
|
"expo-store-review": "~57.0.1",
|
|
90
|
-
"expo-symbols": "~57.0.
|
|
90
|
+
"expo-symbols": "~57.0.2",
|
|
91
91
|
"expo-system-ui": "~57.0.2",
|
|
92
|
-
"expo-task-manager": "~57.0.
|
|
92
|
+
"expo-task-manager": "~57.0.8",
|
|
93
93
|
"expo-tracking-transparency": "~57.0.1",
|
|
94
|
-
"expo-updates": "~57.0.
|
|
94
|
+
"expo-updates": "~57.0.12",
|
|
95
95
|
"expo-video-thumbnails": "~57.0.1",
|
|
96
96
|
"expo-video": "~57.0.2",
|
|
97
97
|
"expo-web-browser": "~57.0.2",
|
|
98
|
-
"expo-widgets": "~57.0.
|
|
98
|
+
"expo-widgets": "~57.0.8",
|
|
99
99
|
"jest-expo": "~57.0.3",
|
|
100
100
|
"lottie-react-native": "~7.3.8",
|
|
101
101
|
"react": "19.2.3",
|
|
@@ -129,32 +129,50 @@ internal final class NativeResponse: SharedObject, ExpoURLSessionTaskDelegate, @
|
|
|
129
129
|
|
|
130
130
|
// MARK: - ExpoURLSessionTaskDelegate implementations
|
|
131
131
|
|
|
132
|
+
// URLSession runs these callbacks on its own queue, racing `state` / sink access on `dispatchQueue`.
|
|
133
|
+
// Hop every callback onto `dispatchQueue` to serialize the whole state machine on one queue.
|
|
134
|
+
|
|
132
135
|
func urlSessionDidStart(_ session: ExpoURLSessionTask) {
|
|
133
|
-
|
|
134
|
-
|
|
136
|
+
dispatchQueue.async { [weak self] in
|
|
137
|
+
guard let self else {
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
if self.isInvalidState(.intialized) {
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
self.state = .started
|
|
135
144
|
}
|
|
136
|
-
state = .started
|
|
137
145
|
}
|
|
138
146
|
|
|
139
147
|
func urlSession(_ session: ExpoURLSessionTask, didReceive response: URLResponse) {
|
|
140
|
-
|
|
141
|
-
|
|
148
|
+
dispatchQueue.async { [weak self] in
|
|
149
|
+
guard let self else {
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
if self.isInvalidState(.started) {
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
self.responseInit = Self.createResponseInit(response: response)
|
|
156
|
+
self.state = .responseReceived
|
|
142
157
|
}
|
|
143
|
-
responseInit = Self.createResponseInit(response: response)
|
|
144
|
-
state = .responseReceived
|
|
145
158
|
}
|
|
146
159
|
|
|
147
160
|
func urlSession(_ session: ExpoURLSessionTask, didReceive data: Data) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
161
|
+
dispatchQueue.async { [weak self] in
|
|
162
|
+
guard let self else {
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
if self.isInvalidState(.responseReceived, .bodyStreamingStarted, .bodyStreamingCanceled) {
|
|
166
|
+
return
|
|
167
|
+
}
|
|
151
168
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
169
|
+
if self.state == .responseReceived {
|
|
170
|
+
self.sink.appendBufferBody(data: data)
|
|
171
|
+
} else if self.state == .bodyStreamingStarted {
|
|
172
|
+
self.emit(event: "didReceiveResponseData", payload: data)
|
|
173
|
+
}
|
|
174
|
+
// no-op in .bodyStreamingCanceled state
|
|
156
175
|
}
|
|
157
|
-
// no-op in .bodyStreamingCanceled state
|
|
158
176
|
}
|
|
159
177
|
|
|
160
178
|
func urlSession(
|
|
@@ -164,22 +182,37 @@ internal final class NativeResponse: SharedObject, ExpoURLSessionTaskDelegate, @
|
|
|
164
182
|
newRequest request: URLRequest,
|
|
165
183
|
completionHandler: @escaping (URLRequest?) -> Void
|
|
166
184
|
) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
185
|
+
dispatchQueue.async { [weak self] in
|
|
186
|
+
guard let self else {
|
|
187
|
+
completionHandler(nil)
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
let shouldFollowRedirects = self.redirectMode == .follow
|
|
191
|
+
completionHandler(shouldFollowRedirects ? request : nil)
|
|
192
|
+
self.redirected = shouldFollowRedirects
|
|
170
193
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
194
|
+
if self.redirectMode == .error {
|
|
195
|
+
let error = FetchRedirectException()
|
|
196
|
+
self.error = error
|
|
197
|
+
if self.state == .bodyStreamingStarted {
|
|
198
|
+
self.emit(event: "didFailWithError", payload: error.localizedDescription)
|
|
199
|
+
}
|
|
200
|
+
self.state = .errorReceived
|
|
201
|
+
self.emit(event: "readyForJSFinalization")
|
|
176
202
|
}
|
|
177
|
-
state = .errorReceived
|
|
178
|
-
emit(event: "readyForJSFinalization")
|
|
179
203
|
}
|
|
180
204
|
}
|
|
181
205
|
|
|
182
206
|
func urlSession(_ session: ExpoURLSessionTask, task: URLSessionTask, didCompleteWithError error: (any Error)?) {
|
|
207
|
+
dispatchQueue.async { [weak self] in
|
|
208
|
+
guard let self else {
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
self.handleDidCompleteWithError(session: session, task: task, error: error)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private func handleDidCompleteWithError(session: ExpoURLSessionTask, task: URLSessionTask, error: (any Error)?) {
|
|
183
216
|
if isInvalidState(.started, .responseReceived, .bodyStreamingStarted, .bodyStreamingCanceled) {
|
|
184
217
|
return
|
|
185
218
|
}
|
|
@@ -202,7 +235,7 @@ internal final class NativeResponse: SharedObject, ExpoURLSessionTaskDelegate, @
|
|
|
202
235
|
guard let self else {
|
|
203
236
|
return
|
|
204
237
|
}
|
|
205
|
-
self.
|
|
238
|
+
self.handleDidCompleteWithError(session: session, task: task, error: error)
|
|
206
239
|
}
|
|
207
240
|
return
|
|
208
241
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo",
|
|
3
|
-
"version": "57.0.
|
|
3
|
+
"version": "57.0.11",
|
|
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": "^57.0.
|
|
71
|
+
"@expo/cli": "^57.0.13",
|
|
72
72
|
"@expo/config": "~57.0.6",
|
|
73
|
-
"@expo/config-plugins": "~57.0.
|
|
73
|
+
"@expo/config-plugins": "~57.0.7",
|
|
74
74
|
"@expo/devtools": "~57.0.1",
|
|
75
75
|
"@expo/dom-webview": "~57.0.1",
|
|
76
76
|
"@expo/fingerprint": "^0.20.6",
|
|
@@ -79,14 +79,14 @@
|
|
|
79
79
|
"@expo/metro": "~56.0.0",
|
|
80
80
|
"@expo/metro-config": "~57.0.7",
|
|
81
81
|
"@ungap/structured-clone": "^1.3.0",
|
|
82
|
-
"babel-preset-expo": "~57.0.
|
|
83
|
-
"expo-asset": "~57.0.
|
|
84
|
-
"expo-constants": "~57.0.
|
|
85
|
-
"expo-file-system": "~57.0.
|
|
82
|
+
"babel-preset-expo": "~57.0.6",
|
|
83
|
+
"expo-asset": "~57.0.9",
|
|
84
|
+
"expo-constants": "~57.0.9",
|
|
85
|
+
"expo-file-system": "~57.0.2",
|
|
86
86
|
"expo-font": "~57.0.1",
|
|
87
87
|
"expo-keep-awake": "~57.0.1",
|
|
88
88
|
"expo-modules-autolinking": "~57.0.9",
|
|
89
|
-
"expo-modules-core": "~57.0.
|
|
89
|
+
"expo-modules-core": "~57.0.10",
|
|
90
90
|
"pretty-format": "^29.7.0",
|
|
91
91
|
"react-refresh": "^0.14.2",
|
|
92
92
|
"whatwg-url-minimum": "^0.1.2"
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
"web-streams-polyfill": "^3.3.2",
|
|
103
103
|
"@expo/dom-webview": "57.0.1",
|
|
104
104
|
"@expo/metro-runtime": "57.0.8",
|
|
105
|
-
"expo-updates": "57.0.
|
|
105
|
+
"expo-updates": "57.0.12"
|
|
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": "
|
|
142
|
+
"gitHead": "e322c877a4d185fb18b27d68945a190cb7f15ce6",
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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('
|
|
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
|
-
|
|
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('
|
|
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(
|
|
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
|
-
):
|
|
105
|
+
): PromiseWithResult<T> {
|
|
102
106
|
const ret = asyncRequireImpl<T>(moduleID, paths, moduleName);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
@@ -163,11 +163,94 @@ export class FetchResponse extends ConcreteNativeResponse implements Response {
|
|
|
163
163
|
metadata: null,
|
|
164
164
|
};
|
|
165
165
|
|
|
166
|
+
// `bodyStreamClosed` is only a fast path — the stream can leave `readable`
|
|
167
|
+
// without it being set, so every controller call must also ask the controller.
|
|
168
|
+
private bodyStreamClosed = false;
|
|
169
|
+
private bodyStreamController: ReadableStreamDefaultController<Uint8Array<ArrayBuffer>> | null =
|
|
170
|
+
null;
|
|
171
|
+
private abortReason: unknown = undefined;
|
|
172
|
+
|
|
166
173
|
constructor(private readonly abortCleanupFunction: AbortSubscriptionCleanupFunction) {
|
|
167
174
|
super();
|
|
168
175
|
this.addListener('readyForJSFinalization', this.finalize);
|
|
169
176
|
}
|
|
170
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Rejects the pending body read with the abort reason and closes the
|
|
180
|
+
* teardown guard so late native events are dropped (#34804). Idempotent,
|
|
181
|
+
* and safe to call before the body stream exists.
|
|
182
|
+
*/
|
|
183
|
+
abort(reason?: unknown): void {
|
|
184
|
+
const abortError =
|
|
185
|
+
reason != null
|
|
186
|
+
? reason
|
|
187
|
+
: typeof DOMException !== 'undefined'
|
|
188
|
+
? new DOMException('The operation was aborted.', 'AbortError')
|
|
189
|
+
: Object.assign(new Error('The operation was aborted.'), { name: 'AbortError' });
|
|
190
|
+
this.abortReason = abortError;
|
|
191
|
+
if (this.bodyStreamClosed) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
this.bodyStreamClosed = true;
|
|
195
|
+
try {
|
|
196
|
+
this.bodyStreamController?.error(abortError);
|
|
197
|
+
} catch {
|
|
198
|
+
// Controller already closed/errored — nothing to propagate.
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* These helpers run on the native emitter's stack, where a throw reaches the
|
|
204
|
+
* global handler. The guard can be stale (internal stream errors bypass
|
|
205
|
+
* `controller.error()`), so check `desiredSize` (null = errored) and catch.
|
|
206
|
+
*/
|
|
207
|
+
private closeBodyStream(
|
|
208
|
+
controller: ReadableStreamDefaultController<Uint8Array<ArrayBuffer>>
|
|
209
|
+
): void {
|
|
210
|
+
if (this.bodyStreamClosed) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
this.bodyStreamClosed = true;
|
|
214
|
+
if (controller.desiredSize == null) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
controller.close();
|
|
219
|
+
} catch {
|
|
220
|
+
// Closed by another path.
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private errorBodyStream(
|
|
225
|
+
controller: ReadableStreamDefaultController<Uint8Array<ArrayBuffer>>,
|
|
226
|
+
error: unknown
|
|
227
|
+
): void {
|
|
228
|
+
if (this.bodyStreamClosed) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
this.bodyStreamClosed = true;
|
|
232
|
+
try {
|
|
233
|
+
controller.error(error);
|
|
234
|
+
} catch {
|
|
235
|
+
// Already closed or errored.
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private enqueueBodyChunk(
|
|
240
|
+
controller: ReadableStreamDefaultController<Uint8Array<ArrayBuffer>>,
|
|
241
|
+
data: Uint8Array<ArrayBuffer>
|
|
242
|
+
): void {
|
|
243
|
+
if (this.bodyStreamClosed || controller.desiredSize == null) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
try {
|
|
247
|
+
controller.enqueue(data);
|
|
248
|
+
} catch {
|
|
249
|
+
// The stream left `readable` between the check and the enqueue — drop
|
|
250
|
+
// the chunk; throwing would error the stream behind the guard's back.
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
171
254
|
// Originals have metadata=null and fall through to the native getter via super.
|
|
172
255
|
// Clones carry a snapshot and stand alone.
|
|
173
256
|
|
|
@@ -199,38 +282,35 @@ export class FetchResponse extends ConcreteNativeResponse implements Response {
|
|
|
199
282
|
const body = this[stateKey].body;
|
|
200
283
|
|
|
201
284
|
if (body.stream == null) {
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
let isControllerClosed = false;
|
|
206
|
-
|
|
285
|
+
// `bodyStreamClosed` prevents enqueuing/closing after the stream is
|
|
286
|
+
// closed, canceled, or aborted — native code may keep sending
|
|
287
|
+
// `didReceiveResponseData` past that point.
|
|
207
288
|
body.stream = new ReadableStream(
|
|
208
289
|
{
|
|
209
290
|
start: (controller) => {
|
|
291
|
+
this.bodyStreamController = controller;
|
|
292
|
+
|
|
293
|
+
// Aborted before the first read — reject immediately.
|
|
294
|
+
if (this.abortReason !== undefined) {
|
|
295
|
+
this.bodyStreamClosed = true;
|
|
296
|
+
controller.error(this.abortReason);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
210
300
|
if (body.streamingState === 'completed') {
|
|
211
301
|
return;
|
|
212
302
|
}
|
|
213
303
|
|
|
214
304
|
this.addListener('didReceiveResponseData', (data: Uint8Array<ArrayBuffer>) => {
|
|
215
|
-
|
|
216
|
-
controller.enqueue(data);
|
|
217
|
-
}
|
|
305
|
+
this.enqueueBodyChunk(controller, data);
|
|
218
306
|
});
|
|
219
307
|
|
|
220
308
|
this.addListener('didComplete', () => {
|
|
221
|
-
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
isControllerClosed = true;
|
|
225
|
-
controller.close();
|
|
309
|
+
this.closeBodyStream(controller);
|
|
226
310
|
});
|
|
227
311
|
|
|
228
312
|
this.addListener('didFailWithError', (error: string) => {
|
|
229
|
-
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
isControllerClosed = true;
|
|
233
|
-
controller.error(new Error(error));
|
|
313
|
+
this.errorBodyStream(controller, new Error(error));
|
|
234
314
|
});
|
|
235
315
|
},
|
|
236
316
|
|
|
@@ -239,25 +319,22 @@ export class FetchResponse extends ConcreteNativeResponse implements Response {
|
|
|
239
319
|
const completedData = await this.startStreaming();
|
|
240
320
|
|
|
241
321
|
if (completedData != null) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
controller.close();
|
|
245
|
-
isControllerClosed = true;
|
|
246
|
-
}
|
|
247
|
-
|
|
322
|
+
this.enqueueBodyChunk(controller, completedData);
|
|
323
|
+
this.closeBodyStream(controller);
|
|
248
324
|
body.streamingState = 'completed';
|
|
249
325
|
} else {
|
|
250
326
|
body.streamingState = 'started';
|
|
251
327
|
}
|
|
252
328
|
} else if (body.streamingState === 'completed') {
|
|
253
|
-
|
|
254
|
-
isControllerClosed = true;
|
|
329
|
+
this.closeBodyStream(controller);
|
|
255
330
|
}
|
|
256
331
|
},
|
|
257
332
|
|
|
258
333
|
cancel: (reason) => {
|
|
334
|
+
// Flag first — the stream is already `closed` when this callback
|
|
335
|
+
// runs, so the guard must not go stale if cancelStreaming throws.
|
|
336
|
+
this.bodyStreamClosed = true;
|
|
259
337
|
this.cancelStreaming(String(reason));
|
|
260
|
-
isControllerClosed = true;
|
|
261
338
|
},
|
|
262
339
|
},
|
|
263
340
|
{
|
|
@@ -138,6 +138,96 @@ describe('FetchResponse', () => {
|
|
|
138
138
|
await readPromise;
|
|
139
139
|
});
|
|
140
140
|
|
|
141
|
+
describe('abort()', () => {
|
|
142
|
+
// abort() covers cancellation via an AbortSignal: fetch() routes it to a native
|
|
143
|
+
// request.cancel() that never reaches the JS controller, leaving the in-flight
|
|
144
|
+
// read hung (expo/expo#34804 / #33549 / #33553).
|
|
145
|
+
|
|
146
|
+
it('rejects the in-flight body read with an AbortError', async () => {
|
|
147
|
+
const response = makeResponse();
|
|
148
|
+
const reader = response.body!.getReader();
|
|
149
|
+
const pending = reader.read();
|
|
150
|
+
|
|
151
|
+
response.abort();
|
|
152
|
+
|
|
153
|
+
await expect(pending).rejects.toMatchObject({ name: 'AbortError' });
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('rejects a read started after abort with an AbortError', async () => {
|
|
157
|
+
const response = makeResponse();
|
|
158
|
+
const body = response.body!; // create the stream, then abort before reading
|
|
159
|
+
response.abort();
|
|
160
|
+
|
|
161
|
+
await expect(body.getReader().read()).rejects.toMatchObject({ name: 'AbortError' });
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('does not deliver or throw on native data that arrives after abort', async () => {
|
|
165
|
+
const response = makeResponse();
|
|
166
|
+
const reader = response.body!.getReader();
|
|
167
|
+
const pending = reader.read();
|
|
168
|
+
|
|
169
|
+
response.abort();
|
|
170
|
+
// The teardown race: native delivers one more data event after cancel.
|
|
171
|
+
const chunk = new TextEncoder().encode('late');
|
|
172
|
+
expect(() => (response as any).emit('didReceiveResponseData', chunk)).not.toThrow();
|
|
173
|
+
expect(() => (response as any).emit('didComplete')).not.toThrow();
|
|
174
|
+
|
|
175
|
+
await expect(pending).rejects.toMatchObject({ name: 'AbortError' });
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('preserves a provided abort reason', async () => {
|
|
179
|
+
const response = makeResponse();
|
|
180
|
+
const reader = response.body!.getReader();
|
|
181
|
+
const pending = reader.read();
|
|
182
|
+
|
|
183
|
+
const reason = new DOMException('user navigated away', 'AbortError');
|
|
184
|
+
response.abort(reason);
|
|
185
|
+
|
|
186
|
+
await expect(pending).rejects.toBe(reason);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('is idempotent and safe to call twice', async () => {
|
|
190
|
+
const response = makeResponse();
|
|
191
|
+
response.body!.getReader();
|
|
192
|
+
response.abort();
|
|
193
|
+
expect(() => response.abort()).not.toThrow();
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('internally errored stream (rejected pull)', () => {
|
|
198
|
+
// When pull() rejects, the streams implementation errors the stream internally
|
|
199
|
+
// without calling controller.error(), so the bodyStreamClosed guard goes stale.
|
|
200
|
+
// Native events landing after that used to throw on the errored stream.
|
|
201
|
+
|
|
202
|
+
it('surfaces the underlying error to the reader', async () => {
|
|
203
|
+
const response = makeResponse();
|
|
204
|
+
|
|
205
|
+
(response as any).startStreaming = async () => {
|
|
206
|
+
throw new Error('native module call failed');
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const reader = response.body!.getReader();
|
|
210
|
+
await expect(reader.read()).rejects.toThrow('native module call failed');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('does not throw on native events that arrive after pull() rejected', async () => {
|
|
214
|
+
const response = makeResponse();
|
|
215
|
+
|
|
216
|
+
(response as any).startStreaming = async () => {
|
|
217
|
+
throw new Error('native module call failed');
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const reader = response.body!.getReader();
|
|
221
|
+
await expect(reader.read()).rejects.toThrow('native module call failed');
|
|
222
|
+
|
|
223
|
+
// The teardown race: native still delivers a chunk, then completion.
|
|
224
|
+
const chunk = new TextEncoder().encode('late');
|
|
225
|
+
expect(() => (response as any).emit('didReceiveResponseData', chunk)).not.toThrow();
|
|
226
|
+
expect(() => (response as any).emit('didComplete')).not.toThrow();
|
|
227
|
+
expect(() => (response as any).emit('didFailWithError', 'late error')).not.toThrow();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
141
231
|
describe('clone()', () => {
|
|
142
232
|
it('returns a Response that exposes the same metadata', () => {
|
|
143
233
|
const response = makeResponse();
|
|
@@ -75,9 +75,12 @@ export async function fetch(
|
|
|
75
75
|
};
|
|
76
76
|
|
|
77
77
|
if (signal && signal.aborted) {
|
|
78
|
-
throw new FetchError('The operation was aborted.');
|
|
78
|
+
throw new FetchError('The operation was aborted.', { cause: signal.reason });
|
|
79
79
|
}
|
|
80
80
|
abortSubscription = addAbortSignalListener(signal, () => {
|
|
81
|
+
// Abort the body stream before canceling the native request, so late
|
|
82
|
+
// native events can't reach an abandoned controller.
|
|
83
|
+
response.abort(signal?.reason);
|
|
81
84
|
request.cancel();
|
|
82
85
|
});
|
|
83
86
|
try {
|