Skip to content

Commit 72f0ace

Browse files
committed
Workaround for expo/expo#39896.
1 parent 703d43f commit 72f0ace

File tree

2 files changed

+238
-57
lines changed

2 files changed

+238
-57
lines changed

react-native/services/Request/index.tsx

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { type Directory, File } from 'expo-file-system'
1+
import { type Directory, File, Paths } from 'expo-file-system'
22
import type { EmptyRequestBody } from '../../types/EmptyRequestBody'
33
import type { FileRequestBody } from '../../types/FileRequestBody'
44
import type { Json } from '../../types/Json'
55
import type { JsonRequestBody } from '../../types/JsonRequestBody'
66
import type { QueryParameter } from '../../types/QueryParameter'
77
import type { QueryParameters } from '../../types/QueryParameters'
88
import type { RequestInterface } from '../../types/RequestInterface'
9+
import type { UuidGeneratorInterface } from '../../types/UuidGeneratorInterface'
910

1011
/**
1112
* Allows HTTP/S requests to be made for JSON and files relative to a base URL.
@@ -23,12 +24,15 @@ export class Request implements RequestInterface {
2324
* otherwise, the returned string (e.g.
2425
* "BEARER 1234") is taken as the
2526
* Authorization header.
27+
* @param fetch Expo's implementation of fetch().
28+
* @param uuidGenerator A generator of UUIDs.
2629
*/
2730
constructor (
2831
baseUrl: string,
2932
private readonly timeoutMilliseconds: number,
3033
private readonly authorizationHeaderFactory: () => null | string,
31-
private readonly fetch: GlobalFetch['fetch']
34+
private readonly fetch: GlobalFetch['fetch'],
35+
private readonly uuidGenerator: UuidGeneratorInterface
3236
) {
3337
if (!/^[a-z]+:\/\//.test(baseUrl)) {
3438
baseUrl = `https://${baseUrl}`
@@ -151,14 +155,24 @@ export class Request implements RequestInterface {
151155
case 'empty':
152156
return null
153157

154-
case 'file':
155-
return new File(...requestBody.fileUri)
158+
case 'file': {
159+
const original = new File(...requestBody.fileUri)
160+
const temporaryCopy = new File(Paths.cache, `${this.uuidGenerator.generate()}.bin`)
161+
original.copy(temporaryCopy)
162+
return temporaryCopy
163+
}
156164

157165
case 'json':
158166
return JSON.stringify(requestBody.value)
159167
}
160168
}
161169

170+
private cleanUpBodyBody (body: null | BodyInit): void {
171+
if (body instanceof File) {
172+
body.delete()
173+
}
174+
}
175+
162176
async withoutResponse<T extends string>(
163177
method: string,
164178
route: string,
@@ -169,17 +183,25 @@ export class Request implements RequestInterface {
169183
): Promise<T> {
170184
return await this.withTimeout(abortSignal, async (signal) => {
171185
const url = this.constructUrl(route, queryParameters)
172-
173-
const response = await this.fetch(url, {
174-
signal,
175-
method,
176-
headers: {
177-
...this.commonHeaders(),
178-
...this.requestBodyHeaders(requestBody),
179-
Accept: 'application/json' // If we do not do this, Laravel will redirect to / in the event of an error, hiding the returned validation error.
180-
},
181-
body: this.requestBodyBody(requestBody)
182-
})
186+
let body: null | BodyInit = null
187+
let response: Response
188+
189+
try {
190+
body = this.requestBodyBody(requestBody)
191+
192+
response = await this.fetch(url, {
193+
signal,
194+
method,
195+
headers: {
196+
...this.commonHeaders(),
197+
...this.requestBodyHeaders(requestBody),
198+
Accept: 'application/json' // If we do not do this, Laravel will redirect to / in the event of an error, hiding the returned validation error.
199+
},
200+
body
201+
})
202+
} finally {
203+
this.cleanUpBodyBody(body)
204+
}
183205

184206
this.checkStatusCode(method, url, response.status, expectedStatusCodes)
185207

@@ -206,17 +228,25 @@ export class Request implements RequestInterface {
206228
> {
207229
return await this.withTimeout(abortSignal, async (signal) => {
208230
const url = this.constructUrl(route, queryParameters)
209-
210-
const response = await this.fetch(url, {
211-
signal,
212-
method,
213-
headers: {
214-
...this.commonHeaders(),
215-
...this.requestBodyHeaders(requestBody),
216-
Accept: 'application/json'
217-
},
218-
body: this.requestBodyBody(requestBody)
219-
})
231+
let body: null | BodyInit = null
232+
let response: Response
233+
234+
try {
235+
body = this.requestBodyBody(requestBody)
236+
237+
response = await this.fetch(url, {
238+
signal,
239+
method,
240+
headers: {
241+
...this.commonHeaders(),
242+
...this.requestBodyHeaders(requestBody),
243+
Accept: 'application/json'
244+
},
245+
body
246+
})
247+
} finally {
248+
this.cleanUpBodyBody(body)
249+
}
220250

221251
this.checkStatusCode(method, url, response.status, expectedStatusCodes as readonly string[])
222252

0 commit comments

Comments
 (0)