Skip to content

Commit 8fbfd49

Browse files
committed
Attempt to upgrade to Expo 54.
1 parent 2c8fa79 commit 8fbfd49

File tree

11 files changed

+2061
-1622
lines changed

11 files changed

+2061
-1622
lines changed

package-lock.json

Lines changed: 1943 additions & 1381 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"license": "MIT",
1818
"devDependencies": {
1919
"@types/jest": "^29.5.4",
20-
"@types/react": "~19.0.10",
20+
"@types/react": "^19.1.10",
2121
"@types/react-test-renderer": "^19.1.0",
2222
"@typescript-eslint/eslint-plugin": "^6.4.1",
2323
"del-cli": "^5.0.0",
@@ -30,12 +30,12 @@
3030
"eslint-plugin-react": "^7.33.2",
3131
"expo-doctor": "^1.1.2",
3232
"jest": "^29.4.0",
33-
"jest-expo": "~53.0.7",
33+
"jest-expo": "^54.0.12",
3434
"metro-react-native-babel-preset": "0.67.0",
3535
"npm-run-all": "^4.1.5",
36-
"react": "19.0.0",
37-
"react-native": "0.79.3",
38-
"typescript": "~5.8.3"
36+
"react": "^19.1.0",
37+
"react-native": "^0.81.4",
38+
"typescript": "^5.9.2"
3939
},
4040
"peerDependencies": {
4141
"react": "*",
@@ -57,18 +57,18 @@
5757
"types": "index.d.ts",
5858
"sideEffects": false,
5959
"dependencies": {
60-
"@sentry/react-native": "6.14.0",
61-
"events": "3.3.0",
62-
"expo": "^53.0.11",
63-
"expo-constants": "~17.1.6",
64-
"expo-crypto": "~14.1.5",
65-
"expo-file-system": "~18.1.10",
66-
"expo-image-picker": "~16.1.4",
67-
"expo-intent-launcher": "~12.1.5",
68-
"expo-media-library": "~17.1.7",
69-
"expo-secure-store": "~14.2.3",
60+
"@sentry/react-native": "~6.20.0",
61+
"events": "^3.3.0",
62+
"expo": "^54.0.9",
63+
"expo-constants": "~18.0.9",
64+
"expo-crypto": "~15.0.7",
65+
"expo-file-system": "~19.0.14",
66+
"expo-image-picker": "~17.0.8",
67+
"expo-intent-launcher": "~13.0.7",
68+
"expo-media-library": "~18.2.0",
69+
"expo-secure-store": "~15.0.7",
7070
"filter-validate-email": "^1.1.3",
71-
"react-native-gesture-handler": "~2.24.0",
72-
"react-native-safe-area-context": "5.4.0"
71+
"react-native-gesture-handler": "~2.28.0",
72+
"react-native-safe-area-context": "~5.6.0"
7373
}
7474
}

react-native/services/FileStore/index.tsx

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as FileSystem from 'expo-file-system'
1+
import { Directory, File, Paths } from 'expo-file-system'
22
import type { FileStoreInterface } from '../../types/FileStoreInterface'
33
import type { UuidGenerator } from '../UuidGenerator'
44

@@ -20,10 +20,8 @@ export class FileStore implements FileStoreInterface {
2020
try {
2121
this.loading = true
2222

23-
await FileSystem.makeDirectoryAsync(
24-
`${FileSystem.documentDirectory}react-native-app-helpers/file-store/${subdirectoryName}`,
25-
{ intermediates: true }
26-
)
23+
const directory = new Directory(Paths.document, 'react-native-app-helpers', 'file-store', subdirectoryName)
24+
directory.create({ intermediates: true })
2725

2826
this.subdirectoryName = subdirectoryName
2927
} finally {
@@ -34,13 +32,13 @@ export class FileStore implements FileStoreInterface {
3432
}
3533
}
3634

37-
generatePath (uuid: string): string {
35+
generatePath (uuid: string): ReadonlyArray<Directory | string> {
3836
if (this.loading) {
3937
throw new Error('The file store is currently loading.')
4038
} else if (this.subdirectoryName === null) {
4139
throw new Error('The file store is not loaded.')
4240
} else {
43-
return `${FileSystem.documentDirectory}react-native-app-helpers/file-store/${this.subdirectoryName}/${uuid}`
41+
return [Paths.document, 'react-native-app-helpers', 'file-store', this.subdirectoryName, uuid]
4442
}
4543
}
4644

@@ -53,9 +51,9 @@ export class FileStore implements FileStoreInterface {
5351
try {
5452
this.operationsInProgress++
5553

56-
return await FileSystem.readDirectoryAsync(
57-
`${FileSystem.documentDirectory}react-native-app-helpers/file-store/${this.subdirectoryName}`
58-
)
54+
const directory = new Directory(Paths.document, 'react-native-app-helpers', 'file-store', this.subdirectoryName)
55+
56+
return (directory.list()).map(x => x.name)
5957
} finally {
6058
this.operationsInProgress--
6159
}
@@ -71,7 +69,8 @@ export class FileStore implements FileStoreInterface {
7169
try {
7270
this.operationsInProgress++
7371

74-
await FileSystem.deleteAsync(this.generatePath(uuid))
72+
const file = new File(...this.generatePath(uuid))
73+
file.delete()
7574
} finally {
7675
this.operationsInProgress--
7776
}
@@ -103,10 +102,9 @@ export class FileStore implements FileStoreInterface {
103102

104103
const output = this.uuidGenerator.generate()
105104

106-
await FileSystem.moveAsync({
107-
from: fileUri,
108-
to: this.generatePath(output)
109-
})
105+
const file = new File(fileUri)
106+
107+
file.move(new File(...this.generatePath(output)))
110108

111109
return output
112110
} finally {
@@ -126,10 +124,9 @@ export class FileStore implements FileStoreInterface {
126124

127125
const output = this.uuidGenerator.generate()
128126

129-
await FileSystem.copyAsync({
130-
from: fileUri,
131-
to: this.generatePath(output)
132-
})
127+
const file = new File(fileUri)
128+
129+
file.copy(new File(...this.generatePath(output)))
133130

134131
return output
135132
} finally {

react-native/services/Request/index.tsx

Lines changed: 24 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as FileSystem from 'expo-file-system'
1+
import { type Directory, File } 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'
@@ -7,14 +7,6 @@ import type { QueryParameter } from '../../types/QueryParameter'
77
import type { QueryParameters } from '../../types/QueryParameters'
88
import type { RequestInterface } from '../../types/RequestInterface'
99

10-
class AbortError extends Error {
11-
constructor () {
12-
super('Aborted.')
13-
14-
this.name = 'AbortError'
15-
}
16-
}
17-
1810
/**
1911
* Allows HTTP/S requests to be made for JSON and files relative to a base URL.
2012
*/
@@ -157,9 +149,11 @@ export class Request implements RequestInterface {
157149
): null | BodyInit {
158150
switch (requestBody.type) {
159151
case 'empty':
160-
case 'file':
161152
return null
162153

154+
case 'file':
155+
return new File(...requestBody.fileUri)
156+
163157
case 'json':
164158
return JSON.stringify(requestBody.value)
165159
}
@@ -176,54 +170,16 @@ export class Request implements RequestInterface {
176170
return await this.withTimeout(abortSignal, async (signal) => {
177171
const url = this.constructUrl(route, queryParameters)
178172

179-
let response: { readonly status: number }
180-
181-
switch (requestBody.type) {
182-
case 'empty':
183-
case 'json':
184-
response = await this.fetch(url, {
185-
signal,
186-
method,
187-
headers: {
188-
...this.commonHeaders(),
189-
...this.requestBodyHeaders(requestBody),
190-
Accept: 'application/json' // If we do not do this, Laravel will redirect to / in the event of an error, hiding the returned validation error.
191-
},
192-
body: this.requestBodyBody(requestBody)
193-
})
194-
break
195-
196-
case 'file': {
197-
const task = FileSystem.createUploadTask(url, requestBody.fileUri, {
198-
uploadType: FileSystem.FileSystemUploadType.BINARY_CONTENT,
199-
headers: {
200-
...this.commonHeaders(),
201-
...this.requestBodyHeaders(requestBody),
202-
Accept: 'application/json' // If we do not do this, Laravel will redirect to / in the event of an error, hiding the returned validation error.
203-
}
204-
})
205-
206-
const eventListener = (): void => {
207-
void task.cancelAsync()
208-
}
209-
210-
try {
211-
signal.addEventListener('abort', eventListener)
212-
213-
const result = await task.uploadAsync()
214-
215-
// NOTE: According to Expo's documentation, this should only return a value or undefined, but it has been observed to return null when the task is aborted.
216-
if (result === undefined || result === null) {
217-
throw new AbortError()
218-
} else {
219-
response = result
220-
}
221-
} finally {
222-
signal.removeEventListener('abort', eventListener)
223-
}
224-
break
225-
}
226-
}
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+
})
227183

228184
this.checkStatusCode(method, url, response.status, expectedStatusCodes)
229185

@@ -271,49 +227,24 @@ export class Request implements RequestInterface {
271227
})
272228
}
273229

274-
async returningFile<T extends string>(
275-
method: 'GET',
230+
async returningFile (
231+
_method: 'GET',
276232
route: string,
277233
requestBody: EmptyRequestBody,
278234
queryParameters: QueryParameters,
279235

280-
// Not yet possible with FileSystem.downloadAsync.
236+
// Not yet possible with File.downloadAsync.
281237
_abortSignal: null,
282238

283-
fileUri: string,
284-
successfulStatusCodes: readonly T[],
285-
unsuccessfulStatusCodes: readonly T[]
286-
): Promise<T> {
239+
fileUri: ReadonlyArray<Directory | string>
240+
): Promise<void> {
287241
const url = this.constructUrl(route, queryParameters)
288242

289-
try {
290-
const response = await FileSystem.downloadAsync(url, fileUri, {
291-
headers: {
292-
...this.commonHeaders(),
293-
...this.requestBodyHeaders(requestBody)
294-
}
295-
})
296-
297-
this.checkStatusCode(method, url, response.status, [
298-
...successfulStatusCodes,
299-
...unsuccessfulStatusCodes
300-
])
301-
302-
// It's possible that the application will close before we hit this line,
303-
// but this is the best we can do unfortunately.
304-
if (unsuccessfulStatusCodes.includes(String(response.status) as T)) {
305-
await FileSystem.deleteAsync(fileUri, { idempotent: true })
243+
await File.downloadFileAsync(url, new File(...fileUri), {
244+
headers: {
245+
...this.commonHeaders(),
246+
...this.requestBodyHeaders(requestBody)
306247
}
307-
308-
return String(response.status) as T
309-
} catch (e) {
310-
// It has been observed that FileSystem.downloadAsync will still create
311-
// files for non-2xx status codes. It's possible that the application
312-
// will close before we hit this line, but this is the best we can do
313-
// unfortunately.
314-
await FileSystem.deleteAsync(fileUri, { idempotent: true })
315-
316-
throw e
317-
}
248+
})
318249
}
319250
}

react-native/services/Request/unit.tsx

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,7 +1002,7 @@ test('get request file response empty', async () => {
10021002
await request.withoutResponse(
10031003
'PUT',
10041004
'example/route',
1005-
{ type: 'file', fileUri: 'Example File Uri' },
1005+
{ type: 'file', fileUri: ['Example', 'File Uri'] },
10061006
{
10071007
'Example Query Parameter A Key': 'Example Query Parameter A Value',
10081008
'Example Query Parameter B Key': 12.34,
@@ -1062,7 +1062,7 @@ test('get request file response empty external abort', async () => {
10621062
const promise = request.withoutResponse(
10631063
'PUT',
10641064
'example/route',
1065-
{ type: 'file', fileUri: 'Example File Uri' },
1065+
{ type: 'file', fileUri: ['Example', 'File Uri'] },
10661066
{
10671067
'Example Query Parameter A Key': 'Example Query Parameter A Value',
10681068
'Example Query Parameter B Key': 12.34,
@@ -1126,7 +1126,7 @@ test('get request file response empty external abort null', async () => {
11261126
const promise = request.withoutResponse(
11271127
'PUT',
11281128
'example/route',
1129-
{ type: 'file', fileUri: 'Example File Uri' },
1129+
{ type: 'file', fileUri: ['Example', 'File Uri'] },
11301130
{
11311131
'Example Query Parameter A Key': 'Example Query Parameter A Value',
11321132
'Example Query Parameter B Key': 12.34,
@@ -1715,7 +1715,7 @@ test('get request empty response file', async () => {
17151715
fetch as unknown as GlobalFetch['fetch']
17161716
)
17171717

1718-
const response = await request.returningFile(
1718+
await request.returningFile(
17191719
'GET',
17201720
'example/route',
17211721
{ type: 'empty' },
@@ -1726,13 +1726,9 @@ test('get request empty response file', async () => {
17261726
'Example Query Parameter D Key': true
17271727
},
17281728
null,
1729-
'Example File Uri',
1730-
['244', '123', '89'],
1731-
['800', '222', '347', '844']
1729+
['Example', 'File Uri']
17321730
)
17331731

1734-
expect(response).toEqual('123')
1735-
17361732
expect(FileSystem.downloadAsync).toBeCalledTimes(1)
17371733
expect(FileSystem.downloadAsync).toBeCalledWith(
17381734
'https://example-base-url.com/example/sub/path/example/route?Example%20Query%20Parameter%20A%20Key=Example%20Query%20Parameter%20A%20Value&Example%20Query%20Parameter%20B%20Key=12.34&Example%20Query%20Parameter%20D%20Key',
@@ -1763,7 +1759,7 @@ test('get request empty response file failure status code', async () => {
17631759
fetch as unknown as GlobalFetch['fetch']
17641760
)
17651761

1766-
const response = await request.returningFile(
1762+
await request.returningFile(
17671763
'GET',
17681764
'example/route',
17691765
{ type: 'empty' },
@@ -1774,13 +1770,9 @@ test('get request empty response file failure status code', async () => {
17741770
'Example Query Parameter D Key': true
17751771
},
17761772
null,
1777-
'Example File Uri',
1778-
['244', '123', '89'],
1779-
['800', '222', '347', '844']
1773+
['Example', 'File Uri']
17801774
)
17811775

1782-
expect(response).toEqual('347')
1783-
17841776
expect(FileSystem.downloadAsync).toBeCalledTimes(1)
17851777
expect(FileSystem.downloadAsync).toBeCalledWith(
17861778
'https://example-base-url.com/example/sub/path/example/route?Example%20Query%20Parameter%20A%20Key=Example%20Query%20Parameter%20A%20Value&Example%20Query%20Parameter%20B%20Key=12.34&Example%20Query%20Parameter%20D%20Key',
@@ -1826,9 +1818,7 @@ test('get request empty response file invalid status code', async () => {
18261818
'Example Query Parameter D Key': true
18271819
},
18281820
null,
1829-
'Example File Uri',
1830-
['244', '123', '89'],
1831-
['800', '222', '347', '844']
1821+
['Example', 'File Uri']
18321822
)
18331823

18341824
await expect(promise).rejects.toEqual(

0 commit comments

Comments
 (0)