expo 52.0.18 → 52.0.19
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/android/src/main/java/expo/modules/fetch/NativeResponse.kt +30 -15
- package/build/devtools/DevToolsPluginClient.d.ts.map +1 -1
- package/build/utils/blobUtils.d.ts.map +1 -0
- package/build/winter/FormData.d.ts +47 -3
- package/build/winter/FormData.d.ts.map +1 -1
- package/build/winter/fetch/FetchResponse.d.ts +4 -1
- package/build/winter/fetch/FetchResponse.d.ts.map +1 -1
- package/build/winter/fetch/NativeRequest.d.ts +1 -0
- package/build/winter/fetch/NativeRequest.d.ts.map +1 -1
- package/build/winter/fetch/RequestUtils.d.ts +0 -14
- package/build/winter/fetch/RequestUtils.d.ts.map +1 -1
- package/build/winter/fetch/convertFormData.d.ts +19 -0
- package/build/winter/fetch/convertFormData.d.ts.map +1 -0
- package/build/winter/fetch/fetch.d.ts.map +1 -1
- package/bundledNativeModules.json +6 -6
- package/ios/Fetch/NativeResponse.swift +8 -1
- package/package.json +7 -7
- package/src/devtools/DevToolsPluginClient.ts +1 -1
- package/src/devtools/MessageFramePacker.ts +1 -1
- package/src/winter/FormData.ts +66 -15
- package/src/winter/__tests__/FormData.test.ios.ts +11 -1
- package/src/winter/fetch/FetchResponse.ts +13 -5
- package/src/winter/fetch/NativeRequest.ts +1 -0
- package/src/winter/fetch/RequestUtils.ts +9 -54
- package/src/winter/fetch/__tests__/RequestUtils-test.ts +0 -52
- package/src/winter/fetch/__tests__/convertFormData-test.native.ts +111 -0
- package/src/winter/fetch/convertFormData.ts +78 -0
- package/src/winter/fetch/fetch.ts +21 -7
- package/build/devtools/blobUtils.d.ts.map +0 -1
- /package/build/{devtools → utils}/blobUtils.d.ts +0 -0
- /package/src/{devtools → utils}/blobUtils.ts +0 -0
package/android/build.gradle
CHANGED
|
@@ -33,7 +33,7 @@ def getRNVersion() {
|
|
|
33
33
|
ensureDependeciesWereEvaluated(project)
|
|
34
34
|
|
|
35
35
|
group = 'host.exp.exponent'
|
|
36
|
-
version = '52.0.
|
|
36
|
+
version = '52.0.19'
|
|
37
37
|
|
|
38
38
|
buildscript {
|
|
39
39
|
// Simple helper that allows the root project to override versions declared by this library.
|
|
@@ -46,7 +46,7 @@ android {
|
|
|
46
46
|
namespace "expo.core"
|
|
47
47
|
defaultConfig {
|
|
48
48
|
versionCode 1
|
|
49
|
-
versionName "52.0.
|
|
49
|
+
versionName "52.0.19"
|
|
50
50
|
consumerProguardFiles("proguard-rules.pro")
|
|
51
51
|
}
|
|
52
52
|
testOptions {
|
|
@@ -72,8 +72,13 @@ internal class NativeResponse(appContext: AppContext, private val coroutineScope
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
fun emitRequestCancelled() {
|
|
75
|
-
error = FetchRequestCancelledException()
|
|
75
|
+
val error = FetchRequestCancelledException()
|
|
76
|
+
this.error = error
|
|
77
|
+
if (state == ResponseState.BODY_STREAMING_STARTED) {
|
|
78
|
+
emit("didFailWithError", error)
|
|
79
|
+
}
|
|
76
80
|
state = ResponseState.ERROR_RECEIVED
|
|
81
|
+
emit("readyForJSFinalization")
|
|
77
82
|
}
|
|
78
83
|
|
|
79
84
|
fun waitForStates(states: List<ResponseState>, callback: (ResponseState) -> Unit) {
|
|
@@ -108,6 +113,7 @@ internal class NativeResponse(appContext: AppContext, private val coroutineScope
|
|
|
108
113
|
}
|
|
109
114
|
error = e
|
|
110
115
|
state = ResponseState.ERROR_RECEIVED
|
|
116
|
+
emit("readyForJSFinalization")
|
|
111
117
|
}
|
|
112
118
|
|
|
113
119
|
override fun onResponse(call: Call, response: Response) {
|
|
@@ -123,6 +129,7 @@ internal class NativeResponse(appContext: AppContext, private val coroutineScope
|
|
|
123
129
|
emit("didComplete")
|
|
124
130
|
}
|
|
125
131
|
this@NativeResponse.state = ResponseState.BODY_COMPLETED
|
|
132
|
+
emit("readyForJSFinalization")
|
|
126
133
|
}
|
|
127
134
|
}
|
|
128
135
|
|
|
@@ -158,22 +165,30 @@ internal class NativeResponse(appContext: AppContext, private val coroutineScope
|
|
|
158
165
|
}
|
|
159
166
|
|
|
160
167
|
private fun pumpResponseBodyStream(stream: BufferedSource) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
try {
|
|
169
|
+
while (!stream.exhausted()) {
|
|
170
|
+
if (isInvalidState(
|
|
171
|
+
ResponseState.RESPONSE_RECEIVED,
|
|
172
|
+
ResponseState.BODY_STREAMING_STARTED,
|
|
173
|
+
ResponseState.BODY_STREAMING_CANCELLED
|
|
174
|
+
)
|
|
175
|
+
) {
|
|
176
|
+
break
|
|
177
|
+
}
|
|
178
|
+
if (state == ResponseState.RESPONSE_RECEIVED) {
|
|
179
|
+
sink.appendBufferBody(stream.buffer.readByteArray())
|
|
180
|
+
} else if (state == ResponseState.BODY_STREAMING_STARTED) {
|
|
181
|
+
emit("didReceiveResponseData", stream.buffer.readByteArray())
|
|
182
|
+
} else {
|
|
183
|
+
break
|
|
184
|
+
}
|
|
169
185
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
emit("
|
|
174
|
-
} else {
|
|
175
|
-
break
|
|
186
|
+
} catch (e: IOException) {
|
|
187
|
+
this.error = e
|
|
188
|
+
if (state == ResponseState.BODY_STREAMING_STARTED) {
|
|
189
|
+
emit("didFailWithError", e)
|
|
176
190
|
}
|
|
191
|
+
state = ResponseState.ERROR_RECEIVED
|
|
177
192
|
}
|
|
178
193
|
}
|
|
179
194
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DevToolsPluginClient.d.ts","sourceRoot":"","sources":["../../src/devtools/DevToolsPluginClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAG5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"DevToolsPluginClient.d.ts","sourceRoot":"","sources":["../../src/devtools/DevToolsPluginClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAG5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,OAAO,KAAK,EAAE,cAAc,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AASpF;;;GAGG;AACH,8BAAsB,oBAAoB;aAYtB,cAAc,EAAE,cAAc;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IAZ3B,SAAS,CAAC,YAAY,EAAE,YAAY,CAAsB;IAE1D,OAAO,CAAC,MAAM,CAAC,cAAc,CAAsD;IACnF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8D;IAEtF,SAAS,CAAC,QAAQ,UAAS;IAC3B,SAAS,CAAC,OAAO,SAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CACR;gBAGT,cAAc,EAAE,cAAc,EAC7B,OAAO,CAAC,yCAA6B;IAKxD;;;OAGG;IACU,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAQvC;;OAEG;IACU,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAWxC;;;;OAIG;IACI,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG;YAQhC,eAAe;IAS7B;;;;OAIG;IACI,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,GAAG,iBAAiB;IAI7F;;;;OAIG;IACI,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAIpF;;OAEG;IACI,WAAW,IAAI,OAAO;IAI7B;;OAEG;IACH,SAAS,CAAC,YAAY,IAAI,OAAO,CAAC,SAAS,CAAC;IAyB5C,SAAS,CAAC,aAAa,UAAW,qBAAqB,UAErD;IAEF,OAAO,CAAC,iBAAiB,CAiBvB;IAEF;;;OAGG;IACI,wBAAwB,IAAI,qBAAqB;CAGzD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blobUtils.d.ts","sourceRoot":"","sources":["../../src/utils/blobUtils.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAKvE;AAED;;GAEG;AACH,wBAAsB,4BAA4B,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CASnF"}
|
|
@@ -1,5 +1,49 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export type ExpoFormDataValue = string | Blob;
|
|
2
|
+
export type ExpoFormDataPart = {
|
|
3
|
+
string: string;
|
|
4
|
+
headers: {
|
|
5
|
+
[name: string]: string;
|
|
6
|
+
};
|
|
7
|
+
} | {
|
|
8
|
+
blob: Blob;
|
|
9
|
+
headers: {
|
|
10
|
+
[name: string]: string;
|
|
11
|
+
};
|
|
12
|
+
name?: string | undefined;
|
|
13
|
+
type?: string | undefined;
|
|
14
|
+
} | {
|
|
15
|
+
uri: string;
|
|
16
|
+
headers: {
|
|
17
|
+
[name: string]: string;
|
|
18
|
+
};
|
|
19
|
+
name?: string | undefined;
|
|
20
|
+
type?: string | undefined;
|
|
4
21
|
};
|
|
22
|
+
export declare class ExpoFormData {
|
|
23
|
+
constructor();
|
|
24
|
+
append(name: string, value: string): void;
|
|
25
|
+
append(name: string, value: Blob, filename?: string): void;
|
|
26
|
+
delete(name: string): void;
|
|
27
|
+
get(name: string): FormDataEntryValue | null;
|
|
28
|
+
getAll(name: string): FormDataEntryValue[];
|
|
29
|
+
has(name: string): boolean;
|
|
30
|
+
set(name: string, value: string): void;
|
|
31
|
+
set(name: string, value: Blob, filename?: string): void;
|
|
32
|
+
append(name: string, value: {
|
|
33
|
+
uri: string;
|
|
34
|
+
name?: string;
|
|
35
|
+
type?: string;
|
|
36
|
+
}): void;
|
|
37
|
+
set(name: string, value: {
|
|
38
|
+
uri: string;
|
|
39
|
+
name?: string;
|
|
40
|
+
type?: string;
|
|
41
|
+
}): void;
|
|
42
|
+
forEach(callback: (value: FormDataEntryValue, key: string, iterable: FormData) => void, thisArg?: unknown): void;
|
|
43
|
+
keys(): IterableIterator<string>;
|
|
44
|
+
values(): IterableIterator<FormDataEntryValue>;
|
|
45
|
+
entries(): IterableIterator<[string, FormDataEntryValue]>;
|
|
46
|
+
[Symbol.iterator](): IterableIterator<[string, FormDataEntryValue]>;
|
|
47
|
+
}
|
|
48
|
+
export declare function installFormDataPatch(formData: typeof FormData): typeof ExpoFormData;
|
|
5
49
|
//# sourceMappingURL=FormData.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormData.d.ts","sourceRoot":"","sources":["../../src/winter/FormData.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"FormData.d.ts","sourceRoot":"","sources":["../../src/winter/FormData.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,IAAI,CAAC;AAC9C,MAAM,MAAM,gBAAgB,GACxB;IAEE,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE;QAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;CACrC,GACD;IAEE,IAAI,EAAE,IAAI,CAAC;IACX,OAAO,EAAE;QAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IACpC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B,GACD;IAEE,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE;QAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IACpC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B,CAAC;AAEN,MAAM,CAAC,OAAO,OAAO,YAAY;;IAE/B,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IACzC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAC1D,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAC1B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI;IAC5C,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,EAAE;IAC1C,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAC1B,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IACtC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAGvD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAChF,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAG7E,OAAO,CACL,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,KAAK,IAAI,EAC9E,OAAO,CAAC,EAAE,OAAO,GAChB,IAAI;IACP,IAAI,IAAI,gBAAgB,CAAC,MAAM,CAAC;IAChC,MAAM,IAAI,gBAAgB,CAAC,kBAAkB,CAAC;IAC9C,OAAO,IAAI,gBAAgB,CAAC,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IACzD,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;CACpE;AAyBD,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,OAAO,QAAQ,GAAG,OAAO,YAAY,CA+HnF"}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { ReadableStream } from 'web-streams-polyfill';
|
|
2
2
|
import type { NativeResponse } from './NativeRequest';
|
|
3
3
|
declare const ConcreteNativeResponse: typeof NativeResponse;
|
|
4
|
+
export type AbortSubscriptionCleanupFunction = () => void;
|
|
4
5
|
/**
|
|
5
6
|
* A response implementation for the `fetch.Response` API.
|
|
6
7
|
*/
|
|
7
8
|
export declare class FetchResponse extends ConcreteNativeResponse implements Response {
|
|
9
|
+
private readonly abortCleanupFunction;
|
|
8
10
|
private streamingStarted;
|
|
11
|
+
constructor(abortCleanupFunction: AbortSubscriptionCleanupFunction);
|
|
9
12
|
get body(): ReadableStream<Uint8Array> | null;
|
|
10
13
|
get headers(): Headers;
|
|
11
14
|
get ok(): boolean;
|
|
@@ -16,7 +19,7 @@ export declare class FetchResponse extends ConcreteNativeResponse implements Res
|
|
|
16
19
|
toString(): string;
|
|
17
20
|
toJSON(): object;
|
|
18
21
|
clone(): FetchResponse;
|
|
19
|
-
private
|
|
22
|
+
private finalize;
|
|
20
23
|
}
|
|
21
24
|
export {};
|
|
22
25
|
//# sourceMappingURL=FetchResponse.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FetchResponse.d.ts","sourceRoot":"","sources":["../../../src/winter/fetch/FetchResponse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,QAAA,MAAM,sBAAsB,uBAA0D,CAAC;AAEvF;;GAEG;AACH,qBAAa,aAAc,SAAQ,sBAAuB,YAAW,QAAQ;
|
|
1
|
+
{"version":3,"file":"FetchResponse.d.ts","sourceRoot":"","sources":["../../../src/winter/fetch/FetchResponse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAGtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,QAAA,MAAM,sBAAsB,uBAA0D,CAAC;AAEvF,MAAM,MAAM,gCAAgC,GAAG,MAAM,IAAI,CAAC;AAE1D;;GAEG;AACH,qBAAa,aAAc,SAAQ,sBAAuB,YAAW,QAAQ;IAG/D,OAAO,CAAC,QAAQ,CAAC,oBAAoB;IAFjD,OAAO,CAAC,gBAAgB,CAAS;gBAEJ,oBAAoB,EAAE,gCAAgC;IAKnF,IAAI,IAAI,IAAI,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CA0B5C;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,EAAE,IAAI,OAAO,CAEhB;IAED,SAAgB,IAAI,aAAa;IAE3B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAKrB,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC;IAY7B,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC;IAK1B,QAAQ,IAAI,MAAM;IAIlB,MAAM,IAAI,MAAM;IAShB,KAAK,IAAI,aAAa;IAItB,OAAO,CAAC,QAAQ,CAQd;CACH"}
|
|
@@ -13,6 +13,7 @@ export type NativeResponseEvents = {
|
|
|
13
13
|
didReceiveResponseData(data: Uint8Array): void;
|
|
14
14
|
didComplete(): void;
|
|
15
15
|
didFailWithError(error: string): void;
|
|
16
|
+
readyForJSFinalization(): void;
|
|
16
17
|
};
|
|
17
18
|
export declare class NativeResponse extends SharedObject<NativeResponseEvents> {
|
|
18
19
|
readonly bodyUsed: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NativeRequest.d.ts","sourceRoot":"","sources":["../../../src/winter/fetch/NativeRequest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,MAAM,MAAM,iBAAiB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;AAEnD,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,YAAY;IAC9C,KAAK,CACV,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,iBAAiB,EAC9B,WAAW,EAAE,UAAU,GAAG,IAAI,GAC7B,OAAO,CAAC,cAAc,CAAC;IACnB,MAAM,IAAI,IAAI;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,sBAAsB,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/C,WAAW,IAAI,IAAI,CAAC;IACpB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"NativeRequest.d.ts","sourceRoot":"","sources":["../../../src/winter/fetch/NativeRequest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,MAAM,MAAM,iBAAiB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;AAEnD,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,YAAY;IAC9C,KAAK,CACV,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,iBAAiB,EAC9B,WAAW,EAAE,UAAU,GAAG,IAAI,GAC7B,OAAO,CAAC,cAAc,CAAC;IACnB,MAAM,IAAI,IAAI;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,sBAAsB,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/C,WAAW,IAAI,IAAI,CAAC;IACpB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,sBAAsB,IAAI,IAAI,CAAC;CAChC,CAAC;AAEF,MAAM,CAAC,OAAO,OAAO,cAAe,SAAQ,YAAY,CAAC,oBAAoB,CAAC;IAC5E,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,iBAAiB,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,cAAc,IAAI,IAAI;IACtB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IACrC,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC;IACnC,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CACxB"}
|
|
@@ -4,20 +4,6 @@ import { type NativeHeadersType } from './NativeRequest';
|
|
|
4
4
|
* convert a ReadableStream to a Uint8Array
|
|
5
5
|
*/
|
|
6
6
|
export declare function convertReadableStreamToUint8ArrayAsync(stream: ReadableStream<Uint8Array>): Promise<Uint8Array>;
|
|
7
|
-
/**
|
|
8
|
-
* Convert FormData to string
|
|
9
|
-
*
|
|
10
|
-
* `uri` is not supported for React Native's FormData.
|
|
11
|
-
* `blob` is not supported for standard FormData.
|
|
12
|
-
*/
|
|
13
|
-
export declare function convertFormData(formData: FormData, boundary?: string): {
|
|
14
|
-
body: string;
|
|
15
|
-
boundary: string;
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* Create mutipart boundary
|
|
19
|
-
*/
|
|
20
|
-
export declare function createBoundary(): string;
|
|
21
7
|
/**
|
|
22
8
|
* Normalize a BodyInit object to a Uint8Array for NativeRequest
|
|
23
9
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RequestUtils.d.ts","sourceRoot":"","sources":["../../../src/winter/fetch/RequestUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"RequestUtils.d.ts","sourceRoot":"","sources":["../../../src/winter/fetch/RequestUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAIzD;;GAEG;AACH,wBAAsB,sCAAsC,CAC1D,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,GACjC,OAAO,CAAC,UAAU,CAAC,CAsBrB;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,QAAQ,GAAG,IAAI,GAAG,SAAS,GAChC,OAAO,CAAC;IAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IAAC,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;CAAE,CAAC,CA6C7E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG,iBAAiB,CAe/F;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,iBAAiB,EAC1B,UAAU,EAAE,iBAAiB,GAC5B,iBAAiB,CAYnB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert FormData to Uint8Array with a boundary
|
|
3
|
+
*
|
|
4
|
+
* `uri` is not supported for React Native's FormData.
|
|
5
|
+
* `blob` is not supported for standard FormData.
|
|
6
|
+
*/
|
|
7
|
+
export declare function convertFormDataAsync(formData: FormData, boundary?: string): Promise<{
|
|
8
|
+
body: Uint8Array;
|
|
9
|
+
boundary: string;
|
|
10
|
+
}>;
|
|
11
|
+
/**
|
|
12
|
+
* Create mutipart boundary
|
|
13
|
+
*/
|
|
14
|
+
export declare function createBoundary(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Merge Uint8Arrays into a single Uint8Array
|
|
17
|
+
*/
|
|
18
|
+
export declare function joinUint8Arrays(arrays: Uint8Array[]): Uint8Array;
|
|
19
|
+
//# sourceMappingURL=convertFormData.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convertFormData.d.ts","sourceRoot":"","sources":["../../../src/winter/fetch/convertFormData.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,QAAQ,EAClB,QAAQ,GAAE,MAAyB,GAClC,OAAO,CAAC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAsCjD;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAOvC;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,UAAU,CAWhE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../src/winter/fetch/fetch.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../src/winter/fetch/fetch.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAyC,MAAM,iBAAiB,CAAC;AAGvF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,wBAAsB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,CAqCxF"}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"expo-apple-authentication": "~7.1.2",
|
|
18
18
|
"expo-application": "~6.0.1",
|
|
19
19
|
"expo-asset": "~11.0.1",
|
|
20
|
-
"expo-audio": "~0.3.
|
|
20
|
+
"expo-audio": "~0.3.1",
|
|
21
21
|
"expo-auth-session": "~6.0.1",
|
|
22
22
|
"expo-av": "~15.0.1",
|
|
23
23
|
"expo-background-fetch": "~13.0.3",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"expo-brightness": "~13.0.2",
|
|
27
27
|
"expo-build-properties": "~0.13.1",
|
|
28
28
|
"expo-calendar": "~14.0.5",
|
|
29
|
-
"expo-camera": "~16.0.
|
|
29
|
+
"expo-camera": "~16.0.10",
|
|
30
30
|
"expo-cellular": "~7.0.1",
|
|
31
31
|
"expo-checkbox": "~4.0.0",
|
|
32
32
|
"expo-clipboard": "~7.0.0",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"expo-device": "~7.0.1",
|
|
38
38
|
"expo-document-picker": "~13.0.1",
|
|
39
39
|
"expo-face-detector": "~13.0.1",
|
|
40
|
-
"expo-file-system": "~18.0.
|
|
40
|
+
"expo-file-system": "~18.0.6",
|
|
41
41
|
"expo-font": "~13.0.1",
|
|
42
42
|
"expo-gl": "~15.0.2",
|
|
43
43
|
"expo-google-app-auth": "~8.3.0",
|
|
@@ -57,12 +57,12 @@
|
|
|
57
57
|
"expo-mail-composer": "~14.0.1",
|
|
58
58
|
"expo-media-library": "~17.0.3",
|
|
59
59
|
"expo-module-template": "~10.15.12",
|
|
60
|
-
"expo-modules-core": "~2.1.
|
|
60
|
+
"expo-modules-core": "~2.1.2",
|
|
61
61
|
"expo-navigation-bar": "~4.0.6",
|
|
62
|
-
"expo-network": "~7.0.
|
|
62
|
+
"expo-network": "~7.0.4",
|
|
63
63
|
"expo-notifications": "~0.29.11",
|
|
64
64
|
"expo-print": "~14.0.2",
|
|
65
|
-
"expo-router": "~4.0.
|
|
65
|
+
"expo-router": "~4.0.13",
|
|
66
66
|
"expo-screen-capture": "~7.0.0",
|
|
67
67
|
"expo-screen-orientation": "~8.0.1",
|
|
68
68
|
"expo-secure-store": "~14.0.0",
|
|
@@ -59,8 +59,13 @@ internal final class NativeResponse: SharedObject, ExpoURLSessionTaskDelegate {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
func emitRequestCanceled() {
|
|
62
|
-
error = FetchRequestCanceledException()
|
|
62
|
+
let error = FetchRequestCanceledException()
|
|
63
|
+
self.error = error
|
|
64
|
+
if state == .bodyStreamingStarted {
|
|
65
|
+
emit(event: "didFailWithError", arguments: error.localizedDescription)
|
|
66
|
+
}
|
|
63
67
|
state = .errorReceived
|
|
68
|
+
emit(event: "readyForJSFinalization")
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
/**
|
|
@@ -197,5 +202,7 @@ internal final class NativeResponse: SharedObject, ExpoURLSessionTaskDelegate {
|
|
|
197
202
|
} else {
|
|
198
203
|
state = .bodyCompleted
|
|
199
204
|
}
|
|
205
|
+
|
|
206
|
+
emit(event: "readyForJSFinalization")
|
|
200
207
|
}
|
|
201
208
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo",
|
|
3
|
-
"version": "52.0.
|
|
3
|
+
"version": "52.0.19",
|
|
4
4
|
"description": "The Expo SDK",
|
|
5
5
|
"main": "src/Expo.ts",
|
|
6
6
|
"module": "src/Expo.ts",
|
|
@@ -68,20 +68,20 @@
|
|
|
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": "0.22.
|
|
71
|
+
"@expo/cli": "0.22.6",
|
|
72
72
|
"@expo/config": "~10.0.6",
|
|
73
73
|
"@expo/config-plugins": "~9.0.12",
|
|
74
|
-
"@expo/fingerprint": "0.11.
|
|
75
|
-
"@expo/metro-config": "0.19.
|
|
74
|
+
"@expo/fingerprint": "0.11.4",
|
|
75
|
+
"@expo/metro-config": "0.19.8",
|
|
76
76
|
"@expo/vector-icons": "^14.0.0",
|
|
77
77
|
"babel-preset-expo": "~12.0.4",
|
|
78
78
|
"expo-asset": "~11.0.1",
|
|
79
79
|
"expo-constants": "~17.0.3",
|
|
80
|
-
"expo-file-system": "~18.0.
|
|
80
|
+
"expo-file-system": "~18.0.6",
|
|
81
81
|
"expo-font": "~13.0.1",
|
|
82
82
|
"expo-keep-awake": "~14.0.1",
|
|
83
83
|
"expo-modules-autolinking": "2.0.4",
|
|
84
|
-
"expo-modules-core": "2.1.
|
|
84
|
+
"expo-modules-core": "2.1.2",
|
|
85
85
|
"fbemitter": "^3.0.0",
|
|
86
86
|
"web-streams-polyfill": "^3.3.2",
|
|
87
87
|
"whatwg-url-without-unicode": "8.0.0-3"
|
|
@@ -112,5 +112,5 @@
|
|
|
112
112
|
"optional": true
|
|
113
113
|
}
|
|
114
114
|
},
|
|
115
|
-
"gitHead": "
|
|
115
|
+
"gitHead": "637acf537a92ac6209bfced71e097ba736b43fef"
|
|
116
116
|
}
|
|
@@ -3,9 +3,9 @@ import { EventEmitter, EventSubscription } from 'fbemitter';
|
|
|
3
3
|
import { MessageFramePacker } from './MessageFramePacker';
|
|
4
4
|
import { WebSocketBackingStore } from './WebSocketBackingStore';
|
|
5
5
|
import { WebSocketWithReconnect } from './WebSocketWithReconnect';
|
|
6
|
-
import { blobToArrayBufferAsync } from './blobUtils';
|
|
7
6
|
import type { ConnectionInfo, DevToolsPluginClientOptions } from './devtools.types';
|
|
8
7
|
import * as logger from './logger';
|
|
8
|
+
import { blobToArrayBufferAsync } from '../utils/blobUtils';
|
|
9
9
|
|
|
10
10
|
interface MessageFramePackerMessageKey {
|
|
11
11
|
pluginName: string;
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
import { blobToArrayBufferAsync } from '
|
|
25
|
+
import { blobToArrayBufferAsync } from '../utils/blobUtils';
|
|
26
26
|
|
|
27
27
|
type MessageKeyTypeBase = string | object;
|
|
28
28
|
type PayloadType = Uint8Array | string | number | null | undefined | object | ArrayBuffer | Blob;
|
package/src/winter/FormData.ts
CHANGED
|
@@ -6,6 +6,54 @@ type ReactNativeFormDataInternal = FormData & {
|
|
|
6
6
|
_parts: [string, string | Blob][];
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
+
export type ExpoFormDataValue = string | Blob;
|
|
10
|
+
export type ExpoFormDataPart =
|
|
11
|
+
| {
|
|
12
|
+
// WinterCG FormData string
|
|
13
|
+
string: string;
|
|
14
|
+
headers: { [name: string]: string };
|
|
15
|
+
}
|
|
16
|
+
| {
|
|
17
|
+
// WinterCG FormData blob in our wrapped form
|
|
18
|
+
blob: Blob;
|
|
19
|
+
headers: { [name: string]: string };
|
|
20
|
+
name?: string | undefined;
|
|
21
|
+
type?: string | undefined;
|
|
22
|
+
}
|
|
23
|
+
| {
|
|
24
|
+
// React Native proprietary local file
|
|
25
|
+
uri: string;
|
|
26
|
+
headers: { [name: string]: string };
|
|
27
|
+
name?: string | undefined;
|
|
28
|
+
type?: string | undefined;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export declare class ExpoFormData {
|
|
32
|
+
constructor();
|
|
33
|
+
append(name: string, value: string): void;
|
|
34
|
+
append(name: string, value: Blob, filename?: string): void;
|
|
35
|
+
delete(name: string): void;
|
|
36
|
+
get(name: string): FormDataEntryValue | null;
|
|
37
|
+
getAll(name: string): FormDataEntryValue[];
|
|
38
|
+
has(name: string): boolean;
|
|
39
|
+
set(name: string, value: string): void;
|
|
40
|
+
set(name: string, value: Blob, filename?: string): void;
|
|
41
|
+
|
|
42
|
+
// React Native proprietary local file
|
|
43
|
+
append(name: string, value: { uri: string; name?: string; type?: string }): void;
|
|
44
|
+
set(name: string, value: { uri: string; name?: string; type?: string }): void;
|
|
45
|
+
|
|
46
|
+
// iterable
|
|
47
|
+
forEach(
|
|
48
|
+
callback: (value: FormDataEntryValue, key: string, iterable: FormData) => void,
|
|
49
|
+
thisArg?: unknown
|
|
50
|
+
): void;
|
|
51
|
+
keys(): IterableIterator<string>;
|
|
52
|
+
values(): IterableIterator<FormDataEntryValue>;
|
|
53
|
+
entries(): IterableIterator<[string, FormDataEntryValue]>;
|
|
54
|
+
[Symbol.iterator](): IterableIterator<[string, FormDataEntryValue]>;
|
|
55
|
+
}
|
|
56
|
+
|
|
9
57
|
function ensureMinArgCount(name: string, args: any[], expected: number) {
|
|
10
58
|
if (args.length < expected) {
|
|
11
59
|
const argName = expected === 2 ? 'arguments' : 'argument';
|
|
@@ -16,29 +64,32 @@ function ensureMinArgCount(name: string, args: any[], expected: number) {
|
|
|
16
64
|
}
|
|
17
65
|
}
|
|
18
66
|
|
|
19
|
-
function normalizeArgs(
|
|
20
|
-
|
|
67
|
+
function normalizeArgs(
|
|
68
|
+
name: string,
|
|
69
|
+
value: any,
|
|
70
|
+
blobFilename: string | undefined
|
|
71
|
+
): [string, File | string] {
|
|
72
|
+
if (value instanceof Blob) {
|
|
73
|
+
value = { type: value.type, name: blobFilename || 'blob', blob: value };
|
|
74
|
+
} else if (typeof value !== 'object') {
|
|
21
75
|
value = String(value);
|
|
22
76
|
}
|
|
23
|
-
// TODO: Add Blob normalization in the future, right now this isn't supported to ensure parity with the rest of the FormData
|
|
24
|
-
// implementation in React Native.
|
|
25
|
-
// https://github.com/facebook/react-native/blob/42dcfdd2cdb59fe545523cb57db6ee32a96b9298/packages/react-native/Libraries/Network/FormData.js#L64
|
|
26
|
-
|
|
27
77
|
return [String(name), value];
|
|
28
78
|
}
|
|
29
79
|
|
|
30
|
-
export function installFormDataPatch(formData: typeof FormData) {
|
|
31
|
-
formData.prototype.append
|
|
80
|
+
export function installFormDataPatch(formData: typeof FormData): typeof ExpoFormData {
|
|
81
|
+
formData.prototype.append = function append(this: ReactNativeFormDataInternal, ...props) {
|
|
32
82
|
ensureMinArgCount('append', props, 2);
|
|
33
|
-
|
|
34
|
-
|
|
83
|
+
// @ts-ignore: When inferred FormData.append from React Native types, it does not support the 3rd blobFilename argument.
|
|
84
|
+
const [name, value, blobFilename] = props;
|
|
85
|
+
this._parts.push(normalizeArgs(name, value, blobFilename));
|
|
35
86
|
};
|
|
36
87
|
|
|
37
88
|
// @ts-ignore: DOM.iterable is disabled for jest compat
|
|
38
|
-
formData.prototype.set
|
|
89
|
+
formData.prototype.set = function set(this: ReactNativeFormDataInternal, ...props) {
|
|
39
90
|
ensureMinArgCount('set', props, 2);
|
|
40
|
-
const [name, value] = props;
|
|
41
|
-
const args = normalizeArgs(name, value);
|
|
91
|
+
const [name, value, blobFilename] = props;
|
|
92
|
+
const args = normalizeArgs(name, value, blobFilename);
|
|
42
93
|
let replaced = false;
|
|
43
94
|
|
|
44
95
|
for (let i = 0; i < this._parts.length; i++) {
|
|
@@ -121,7 +172,7 @@ export function installFormDataPatch(formData: typeof FormData) {
|
|
|
121
172
|
|
|
122
173
|
// Required for RSC: https://github.com/facebook/react/blob/985747f81033833dca22f30b0c04704dd4bd3714/packages/react-server/src/ReactFlightServer.js#L2117
|
|
123
174
|
// @ts-ignore: DOM.iterable is disabled for jest compat
|
|
124
|
-
formData.prototype.entries
|
|
175
|
+
formData.prototype.entries ??= function* entries(
|
|
125
176
|
this: ReactNativeFormDataInternal
|
|
126
177
|
): IterableIterator<[string, FormDataEntryValue]> {
|
|
127
178
|
for (const part of this._parts) {
|
|
@@ -152,5 +203,5 @@ export function installFormDataPatch(formData: typeof FormData) {
|
|
|
152
203
|
// @ts-ignore: DOM.iterable is disabled for jest compat
|
|
153
204
|
formData.prototype[Symbol.iterator] = formData.prototype.entries;
|
|
154
205
|
|
|
155
|
-
return formData;
|
|
206
|
+
return formData as unknown as typeof ExpoFormData;
|
|
156
207
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable no-global-assign */
|
|
2
|
-
import { installFormDataPatch } from '../FormData';
|
|
2
|
+
import { installFormDataPatch, type ExpoFormData } from '../FormData';
|
|
3
3
|
|
|
4
4
|
const jestFormDataPolyfill = FormData;
|
|
5
5
|
|
|
@@ -48,6 +48,16 @@ describe('FormData', () => {
|
|
|
48
48
|
a.delete('a');
|
|
49
49
|
expect(a.get('a')).toBe(null);
|
|
50
50
|
});
|
|
51
|
+
|
|
52
|
+
it(`supports react-native local uri`, () => {
|
|
53
|
+
const a = new FormData() as ExpoFormData;
|
|
54
|
+
a.append('a', { uri: 'file:///path/to/test.jpg', type: 'image/jpeg', name: 'test.jpg' });
|
|
55
|
+
expect(a.get('a')).toEqual({
|
|
56
|
+
uri: 'file:///path/to/test.jpg',
|
|
57
|
+
type: 'image/jpeg',
|
|
58
|
+
name: 'test.jpg',
|
|
59
|
+
});
|
|
60
|
+
});
|
|
51
61
|
});
|
|
52
62
|
|
|
53
63
|
describe('getters', () => {
|
|
@@ -5,12 +5,19 @@ import type { NativeResponse } from './NativeRequest';
|
|
|
5
5
|
|
|
6
6
|
const ConcreteNativeResponse = ExpoFetchModule.NativeResponse as typeof NativeResponse;
|
|
7
7
|
|
|
8
|
+
export type AbortSubscriptionCleanupFunction = () => void;
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* A response implementation for the `fetch.Response` API.
|
|
10
12
|
*/
|
|
11
13
|
export class FetchResponse extends ConcreteNativeResponse implements Response {
|
|
12
14
|
private streamingStarted = false;
|
|
13
15
|
|
|
16
|
+
constructor(private readonly abortCleanupFunction: AbortSubscriptionCleanupFunction) {
|
|
17
|
+
super();
|
|
18
|
+
this.addListener('readyForJSFinalization', this.finalize);
|
|
19
|
+
}
|
|
20
|
+
|
|
14
21
|
get body(): ReadableStream<Uint8Array> | null {
|
|
15
22
|
const response = this;
|
|
16
23
|
return new ReadableStream({
|
|
@@ -20,12 +27,10 @@ export class FetchResponse extends ConcreteNativeResponse implements Response {
|
|
|
20
27
|
});
|
|
21
28
|
|
|
22
29
|
response.addListener('didComplete', () => {
|
|
23
|
-
response.removeAllRegisteredListeners();
|
|
24
30
|
controller.close();
|
|
25
31
|
});
|
|
26
32
|
|
|
27
33
|
response.addListener('didFailWithError', (error: string) => {
|
|
28
|
-
response.removeAllRegisteredListeners();
|
|
29
34
|
controller.error(new Error(error));
|
|
30
35
|
});
|
|
31
36
|
},
|
|
@@ -36,7 +41,6 @@ export class FetchResponse extends ConcreteNativeResponse implements Response {
|
|
|
36
41
|
}
|
|
37
42
|
},
|
|
38
43
|
cancel(reason) {
|
|
39
|
-
response.removeAllRegisteredListeners();
|
|
40
44
|
response.cancelStreaming(String(reason));
|
|
41
45
|
},
|
|
42
46
|
});
|
|
@@ -91,9 +95,13 @@ export class FetchResponse extends ConcreteNativeResponse implements Response {
|
|
|
91
95
|
throw new Error('Not implemented');
|
|
92
96
|
}
|
|
93
97
|
|
|
94
|
-
private
|
|
98
|
+
private finalize = (): void => {
|
|
99
|
+
this.removeListener('readyForJSFinalization', this.finalize);
|
|
100
|
+
|
|
101
|
+
this.abortCleanupFunction();
|
|
102
|
+
|
|
95
103
|
this.removeAllListeners('didReceiveResponseData');
|
|
96
104
|
this.removeAllListeners('didComplete');
|
|
97
105
|
this.removeAllListeners('didFailWithError');
|
|
98
|
-
}
|
|
106
|
+
};
|
|
99
107
|
}
|
|
@@ -21,6 +21,7 @@ export type NativeResponseEvents = {
|
|
|
21
21
|
didReceiveResponseData(data: Uint8Array): void;
|
|
22
22
|
didComplete(): void;
|
|
23
23
|
didFailWithError(error: string): void;
|
|
24
|
+
readyForJSFinalization(): void;
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
export declare class NativeResponse extends SharedObject<NativeResponseEvents> {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { ReadableStream } from 'web-streams-polyfill';
|
|
2
2
|
|
|
3
3
|
import { type NativeHeadersType } from './NativeRequest';
|
|
4
|
+
import { convertFormDataAsync } from './convertFormData';
|
|
5
|
+
import { blobToArrayBufferAsync } from '../../utils/blobUtils';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* convert a ReadableStream to a Uint8Array
|
|
@@ -31,56 +33,6 @@ export async function convertReadableStreamToUint8ArrayAsync(
|
|
|
31
33
|
return result;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
/**
|
|
35
|
-
* Convert FormData to string
|
|
36
|
-
*
|
|
37
|
-
* `uri` is not supported for React Native's FormData.
|
|
38
|
-
* `blob` is not supported for standard FormData.
|
|
39
|
-
*/
|
|
40
|
-
export function convertFormData(
|
|
41
|
-
formData: FormData,
|
|
42
|
-
boundary: string = createBoundary()
|
|
43
|
-
): { body: string; boundary: string } {
|
|
44
|
-
// @ts-expect-error: React Native's FormData is not compatible with the web's FormData
|
|
45
|
-
if (typeof formData.getParts !== 'function') {
|
|
46
|
-
throw new Error('Unsupported FormData implementation');
|
|
47
|
-
}
|
|
48
|
-
// @ts-expect-error: React Native's FormData is not compatible with the web's FormData
|
|
49
|
-
const parts: FormDataPart[] = formData.getParts();
|
|
50
|
-
|
|
51
|
-
const results: string[] = [];
|
|
52
|
-
for (const entry of parts) {
|
|
53
|
-
results.push(`--${boundary}\r\n`);
|
|
54
|
-
for (const [headerKey, headerValue] of Object.entries(entry.headers)) {
|
|
55
|
-
results.push(`${headerKey}: ${headerValue}\r\n`);
|
|
56
|
-
}
|
|
57
|
-
results.push(`\r\n`);
|
|
58
|
-
// @ts-expect-error: TypeScript doesn't know about the `string` property
|
|
59
|
-
if (entry.string != null) {
|
|
60
|
-
// @ts-expect-error: TypeScript doesn't know about the `string` property
|
|
61
|
-
results.push(entry.string);
|
|
62
|
-
} else {
|
|
63
|
-
throw new Error('Unsupported FormDataPart implementation');
|
|
64
|
-
}
|
|
65
|
-
results.push(`\r\n`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
results.push(`--${boundary}--\r\n`);
|
|
69
|
-
return { body: results.join(''), boundary };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Create mutipart boundary
|
|
74
|
-
*/
|
|
75
|
-
export function createBoundary(): string {
|
|
76
|
-
const boundaryChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
77
|
-
let boundary = '----ExpoFetchFormBoundary';
|
|
78
|
-
for (let i = 0; i < 16; i++) {
|
|
79
|
-
boundary += boundaryChars.charAt(Math.floor(Math.random() * boundaryChars.length));
|
|
80
|
-
}
|
|
81
|
-
return boundary;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
36
|
/**
|
|
85
37
|
* Normalize a BodyInit object to a Uint8Array for NativeRequest
|
|
86
38
|
*/
|
|
@@ -105,7 +57,10 @@ export async function normalizeBodyInitAsync(
|
|
|
105
57
|
}
|
|
106
58
|
|
|
107
59
|
if (body instanceof Blob) {
|
|
108
|
-
return {
|
|
60
|
+
return {
|
|
61
|
+
body: new Uint8Array(await blobToArrayBufferAsync(body)),
|
|
62
|
+
overriddenHeaders: [['Content-Type', body.type]],
|
|
63
|
+
};
|
|
109
64
|
}
|
|
110
65
|
|
|
111
66
|
if (body instanceof URLSearchParams) {
|
|
@@ -119,10 +74,10 @@ export async function normalizeBodyInitAsync(
|
|
|
119
74
|
}
|
|
120
75
|
|
|
121
76
|
if (body instanceof FormData) {
|
|
122
|
-
const { body: result, boundary } =
|
|
123
|
-
|
|
77
|
+
const { body: result, boundary } = await convertFormDataAsync(body);
|
|
78
|
+
|
|
124
79
|
return {
|
|
125
|
-
body:
|
|
80
|
+
body: result,
|
|
126
81
|
overriddenHeaders: [['Content-Type', `multipart/form-data; boundary=${boundary}`]],
|
|
127
82
|
};
|
|
128
83
|
}
|
|
@@ -7,58 +7,12 @@ import { ReadableStream } from 'web-streams-polyfill';
|
|
|
7
7
|
|
|
8
8
|
import { type NativeHeadersType } from '../NativeRequest';
|
|
9
9
|
import {
|
|
10
|
-
convertFormData,
|
|
11
10
|
convertReadableStreamToUint8ArrayAsync,
|
|
12
|
-
createBoundary,
|
|
13
11
|
normalizeBodyInitAsync,
|
|
14
12
|
normalizeHeadersInit,
|
|
15
13
|
overrideHeaders,
|
|
16
14
|
} from '../RequestUtils';
|
|
17
15
|
|
|
18
|
-
describe(convertFormData, () => {
|
|
19
|
-
it('should convert react-native FormData to a string with boundary', () => {
|
|
20
|
-
const formData = new RNFormData();
|
|
21
|
-
formData.append('foo', 'foo');
|
|
22
|
-
formData.append('bar', 'bar');
|
|
23
|
-
const boundary = '----ExpoFetchFormBoundary0000000000000000';
|
|
24
|
-
const { body, boundary: resultBoundary } = convertFormData(formData, boundary);
|
|
25
|
-
expect(body).toMatchInlineSnapshot(`
|
|
26
|
-
"------ExpoFetchFormBoundary0000000000000000
|
|
27
|
-
content-disposition: form-data; name="foo"
|
|
28
|
-
|
|
29
|
-
foo
|
|
30
|
-
------ExpoFetchFormBoundary0000000000000000
|
|
31
|
-
content-disposition: form-data; name="bar"
|
|
32
|
-
|
|
33
|
-
bar
|
|
34
|
-
------ExpoFetchFormBoundary0000000000000000--
|
|
35
|
-
"
|
|
36
|
-
`);
|
|
37
|
-
expect(resultBoundary).toBe(boundary);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('should throw an error if the react-native FormData passing an uri', () => {
|
|
41
|
-
const formData = new RNFormData();
|
|
42
|
-
formData.append('foo', {
|
|
43
|
-
uri: 'file:/path/to/test.jpg',
|
|
44
|
-
type: 'image/jpeg',
|
|
45
|
-
name: 'test.jpg',
|
|
46
|
-
});
|
|
47
|
-
expect(() => {
|
|
48
|
-
convertFormData(formData);
|
|
49
|
-
}).toThrow(/Unsupported FormDataPart implementation/);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('should throw an error if passing brower compatible FormData', () => {
|
|
53
|
-
const formData = new globalThis.FormData();
|
|
54
|
-
formData.append('foo', 'foo');
|
|
55
|
-
formData.append('blob', new Blob());
|
|
56
|
-
expect(() => {
|
|
57
|
-
convertFormData(formData);
|
|
58
|
-
}).toThrow(/Unsupported FormData implementation/);
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
16
|
describe(convertReadableStreamToUint8ArrayAsync, () => {
|
|
63
17
|
it('should convert a readable stream to a Uint8Array', async () => {
|
|
64
18
|
const stream = new ReadableStream<Uint8Array>({
|
|
@@ -95,12 +49,6 @@ describe(convertReadableStreamToUint8ArrayAsync, () => {
|
|
|
95
49
|
});
|
|
96
50
|
});
|
|
97
51
|
|
|
98
|
-
describe(createBoundary, () => {
|
|
99
|
-
it('should return a boundary string with ExpoFetchFormBoundary prefix plus 16 random chars', () => {
|
|
100
|
-
expect(createBoundary()).toMatch(/^----ExpoFetchFormBoundary[\w]{16}$/);
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
52
|
describe(normalizeBodyInitAsync, () => {
|
|
105
53
|
let originalFormData;
|
|
106
54
|
beforeAll(() => {
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import RNFormData from 'react-native/Libraries/Network/FormData';
|
|
2
|
+
import { TextDecoder, TextEncoder } from 'util';
|
|
3
|
+
|
|
4
|
+
import { installFormDataPatch } from '../../FormData';
|
|
5
|
+
import { createBoundary, convertFormDataAsync, joinUint8Arrays } from '../convertFormData';
|
|
6
|
+
|
|
7
|
+
// @ts-ignore - TextDecoder and TextEncoder are not defined in native jest environments.
|
|
8
|
+
globalThis.TextDecoder ??= TextDecoder;
|
|
9
|
+
globalThis.TextEncoder ??= TextEncoder;
|
|
10
|
+
|
|
11
|
+
const ExpoFormData = installFormDataPatch(RNFormData);
|
|
12
|
+
|
|
13
|
+
describe(convertFormDataAsync, () => {
|
|
14
|
+
it('should convert string', async () => {
|
|
15
|
+
const formData = new ExpoFormData();
|
|
16
|
+
formData.append('foo', 'foo');
|
|
17
|
+
formData.append('bar', 'bar');
|
|
18
|
+
const boundary = '----ExpoFetchFormBoundary0000000000000000';
|
|
19
|
+
const { body, boundary: resultBoundary } = await convertFormDataAsync(formData, boundary);
|
|
20
|
+
expect(new TextDecoder().decode(body)).toMatchInlineSnapshot(`
|
|
21
|
+
"------ExpoFetchFormBoundary0000000000000000
|
|
22
|
+
content-disposition: form-data; name="foo"
|
|
23
|
+
|
|
24
|
+
foo
|
|
25
|
+
------ExpoFetchFormBoundary0000000000000000
|
|
26
|
+
content-disposition: form-data; name="bar"
|
|
27
|
+
|
|
28
|
+
bar
|
|
29
|
+
------ExpoFetchFormBoundary0000000000000000--
|
|
30
|
+
"
|
|
31
|
+
`);
|
|
32
|
+
expect(resultBoundary).toBe(boundary);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it(`should convert blob`, async () => {
|
|
36
|
+
const formData = new ExpoFormData();
|
|
37
|
+
const blob = new Blob(['hello blob'], { type: 'text/plain' });
|
|
38
|
+
formData.append('blob', blob, 'blobFile');
|
|
39
|
+
const boundary = '----ExpoFetchFormBoundary0000000000000000';
|
|
40
|
+
const { body, boundary: resultBoundary } = await convertFormDataAsync(formData, boundary);
|
|
41
|
+
expect(new TextDecoder().decode(body)).toMatchInlineSnapshot(`
|
|
42
|
+
"------ExpoFetchFormBoundary0000000000000000
|
|
43
|
+
content-disposition: form-data; name="blob"; filename="blobFile"; filename*=utf-8''blobFile
|
|
44
|
+
content-type: text/plain
|
|
45
|
+
|
|
46
|
+
hello blob
|
|
47
|
+
------ExpoFetchFormBoundary0000000000000000--
|
|
48
|
+
"
|
|
49
|
+
`);
|
|
50
|
+
expect(resultBoundary).toBe(boundary);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it(`should convert expo-file-system FileBlob`, async () => {
|
|
54
|
+
const formData = new ExpoFormData();
|
|
55
|
+
const mockFileBlob = {
|
|
56
|
+
file: {
|
|
57
|
+
bytes: () => new Uint8Array([65, 66, 67]),
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
// @ts-ignore
|
|
61
|
+
formData.append('blob', mockFileBlob);
|
|
62
|
+
const boundary = '----ExpoFetchFormBoundary0000000000000000';
|
|
63
|
+
const { body, boundary: resultBoundary } = await convertFormDataAsync(formData, boundary);
|
|
64
|
+
expect(new TextDecoder().decode(body)).toMatchInlineSnapshot(`
|
|
65
|
+
"------ExpoFetchFormBoundary0000000000000000
|
|
66
|
+
content-disposition: form-data; name="blob"
|
|
67
|
+
|
|
68
|
+
ABC
|
|
69
|
+
------ExpoFetchFormBoundary0000000000000000--
|
|
70
|
+
"
|
|
71
|
+
`);
|
|
72
|
+
expect(resultBoundary).toBe(boundary);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should throw an error if the react-native FormData passing an uri', async () => {
|
|
76
|
+
const formData = new ExpoFormData();
|
|
77
|
+
formData.append('foo', {
|
|
78
|
+
uri: 'file:/path/to/test.jpg',
|
|
79
|
+
type: 'image/jpeg',
|
|
80
|
+
name: 'test.jpg',
|
|
81
|
+
});
|
|
82
|
+
expect(convertFormDataAsync(formData)).rejects.toThrow(
|
|
83
|
+
/Unsupported FormDataPart implementation/
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe(createBoundary, () => {
|
|
89
|
+
it('should return a boundary string with ExpoFetchFormBoundary prefix plus 16 random chars', () => {
|
|
90
|
+
expect(createBoundary()).toMatch(/^----ExpoFetchFormBoundary[\w]{16}$/);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe(joinUint8Arrays, () => {
|
|
95
|
+
it(`should join multiple uint8 arrays`, () => {
|
|
96
|
+
const array1 = new Uint8Array([1, 2]);
|
|
97
|
+
const array2 = new Uint8Array([3, 4]);
|
|
98
|
+
const result = joinUint8Arrays([array1, array2]);
|
|
99
|
+
const expected = new Uint8Array([1, 2, 3, 4]);
|
|
100
|
+
expect(result).toEqual(expected);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it(`should join 0 size arrays correctly`, () => {
|
|
104
|
+
const array1 = new Uint8Array([1, 2]);
|
|
105
|
+
const array2 = new Uint8Array([]);
|
|
106
|
+
const array3 = new Uint8Array([3, 4]);
|
|
107
|
+
const result = joinUint8Arrays([array1, array2, array3]);
|
|
108
|
+
const expected = new Uint8Array([1, 2, 3, 4]);
|
|
109
|
+
expect(result).toEqual(expected);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { blobToArrayBufferAsync } from '../../utils/blobUtils';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Convert FormData to Uint8Array with a boundary
|
|
5
|
+
*
|
|
6
|
+
* `uri` is not supported for React Native's FormData.
|
|
7
|
+
* `blob` is not supported for standard FormData.
|
|
8
|
+
*/
|
|
9
|
+
export async function convertFormDataAsync(
|
|
10
|
+
formData: FormData,
|
|
11
|
+
boundary: string = createBoundary()
|
|
12
|
+
): Promise<{ body: Uint8Array; boundary: string }> {
|
|
13
|
+
// @ts-expect-error: React Native's FormData is not compatible with the web's FormData
|
|
14
|
+
if (typeof formData.getParts !== 'function') {
|
|
15
|
+
throw new Error('Unsupported FormData implementation');
|
|
16
|
+
}
|
|
17
|
+
// @ts-expect-error: React Native's FormData is not 100% compatible with ours
|
|
18
|
+
const parts: ExpoFormDataPart[] = formData.getParts();
|
|
19
|
+
|
|
20
|
+
const results: (Uint8Array | string)[] = [];
|
|
21
|
+
for (const entry of parts) {
|
|
22
|
+
results.push(`--${boundary}\r\n`);
|
|
23
|
+
for (const [headerKey, headerValue] of Object.entries(entry.headers)) {
|
|
24
|
+
results.push(`${headerKey}: ${headerValue}\r\n`);
|
|
25
|
+
}
|
|
26
|
+
results.push(`\r\n`);
|
|
27
|
+
if ('string' in entry) {
|
|
28
|
+
results.push(entry.string);
|
|
29
|
+
} else if ('file' in entry) {
|
|
30
|
+
results.push(entry.file.bytes());
|
|
31
|
+
} else if ('blob' in entry) {
|
|
32
|
+
results.push(new Uint8Array(await blobToArrayBufferAsync(entry.blob)));
|
|
33
|
+
} else {
|
|
34
|
+
throw new Error('Unsupported FormDataPart implementation');
|
|
35
|
+
}
|
|
36
|
+
results.push(`\r\n`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
results.push(`--${boundary}--\r\n`);
|
|
40
|
+
const arrays = results.map((result) => {
|
|
41
|
+
if (typeof result === 'string') {
|
|
42
|
+
// @ts-ignore: TextEncoder is not available in all environments
|
|
43
|
+
return new TextEncoder().encode(result);
|
|
44
|
+
} else {
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
}) as Uint8Array[];
|
|
48
|
+
|
|
49
|
+
return { body: joinUint8Arrays(arrays), boundary };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create mutipart boundary
|
|
54
|
+
*/
|
|
55
|
+
export function createBoundary(): string {
|
|
56
|
+
const boundaryChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
57
|
+
let boundary = '----ExpoFetchFormBoundary';
|
|
58
|
+
for (let i = 0; i < 16; i++) {
|
|
59
|
+
boundary += boundaryChars.charAt(Math.floor(Math.random() * boundaryChars.length));
|
|
60
|
+
}
|
|
61
|
+
return boundary;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Merge Uint8Arrays into a single Uint8Array
|
|
66
|
+
*/
|
|
67
|
+
export function joinUint8Arrays(arrays: Uint8Array[]): Uint8Array {
|
|
68
|
+
const totalLength: number = arrays.reduce((acc: number, arr: Uint8Array) => acc + arr.length, 0);
|
|
69
|
+
const result: Uint8Array = new Uint8Array(totalLength);
|
|
70
|
+
|
|
71
|
+
let offset: number = 0;
|
|
72
|
+
arrays.forEach((array: Uint8Array) => {
|
|
73
|
+
result.set(array, offset);
|
|
74
|
+
offset += array.length;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { ExpoFetchModule } from './ExpoFetchModule';
|
|
2
2
|
import { FetchError } from './FetchErrors';
|
|
3
|
-
import { FetchResponse } from './FetchResponse';
|
|
3
|
+
import { FetchResponse, type AbortSubscriptionCleanupFunction } from './FetchResponse';
|
|
4
4
|
import { NativeRequest, NativeRequestInit } from './NativeRequest';
|
|
5
5
|
import { normalizeBodyInitAsync, normalizeHeadersInit, overrideHeaders } from './RequestUtils';
|
|
6
6
|
import type { FetchRequestInit } from './fetch.types';
|
|
7
7
|
|
|
8
8
|
export async function fetch(url: string, init?: FetchRequestInit): Promise<FetchResponse> {
|
|
9
|
-
|
|
9
|
+
let abortSubscription: AbortSubscriptionCleanupFunction | null = null;
|
|
10
|
+
|
|
11
|
+
const response = new FetchResponse(() => {
|
|
12
|
+
abortSubscription?.();
|
|
13
|
+
});
|
|
10
14
|
const request = new ExpoFetchModule.NativeRequest(response) as NativeRequest;
|
|
11
15
|
|
|
12
16
|
let headers = normalizeHeadersInit(init?.headers);
|
|
@@ -25,10 +29,9 @@ export async function fetch(url: string, init?: FetchRequestInit): Promise<Fetch
|
|
|
25
29
|
if (init?.signal && init.signal.aborted) {
|
|
26
30
|
throw new FetchError('The operation was aborted.');
|
|
27
31
|
}
|
|
28
|
-
|
|
32
|
+
abortSubscription = addAbortSignalListener(init?.signal, () => {
|
|
29
33
|
request.cancel();
|
|
30
|
-
};
|
|
31
|
-
init?.signal?.addEventListener('abort', abortHandler);
|
|
34
|
+
});
|
|
32
35
|
try {
|
|
33
36
|
await request.start(url, nativeRequestInit, requestBody);
|
|
34
37
|
} catch (e: unknown) {
|
|
@@ -37,8 +40,19 @@ export async function fetch(url: string, init?: FetchRequestInit): Promise<Fetch
|
|
|
37
40
|
} else {
|
|
38
41
|
throw new FetchError(String(e));
|
|
39
42
|
}
|
|
40
|
-
} finally {
|
|
41
|
-
init?.signal?.removeEventListener('abort', abortHandler);
|
|
42
43
|
}
|
|
43
44
|
return response;
|
|
44
45
|
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A wrapper of `AbortSignal.addEventListener` that returns a cleanup function.
|
|
49
|
+
*/
|
|
50
|
+
function addAbortSignalListener(
|
|
51
|
+
signal: AbortSignal | undefined,
|
|
52
|
+
listener: Parameters<AbortSignal['addEventListener']>[1]
|
|
53
|
+
): AbortSubscriptionCleanupFunction {
|
|
54
|
+
signal?.addEventListener('abort', listener);
|
|
55
|
+
return () => {
|
|
56
|
+
signal?.removeEventListener('abort', listener);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"blobUtils.d.ts","sourceRoot":"","sources":["../../src/devtools/blobUtils.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAKvE;AAED;;GAEG;AACH,wBAAsB,4BAA4B,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CASnF"}
|
|
File without changes
|
|
File without changes
|