mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import RNFS from 'react-native-fs'
2import {BskyAgent, ComAtprotoRepoUploadBlob} from '@atproto/api'
3
4/**
5 * @param encoding Allows overriding the blob's type
6 */
7export async function uploadBlob(
8 agent: BskyAgent,
9 input: string | Blob,
10 encoding?: string,
11): Promise<ComAtprotoRepoUploadBlob.Response> {
12 if (typeof input === 'string' && input.startsWith('file:')) {
13 const blob = await asBlob(input)
14 return agent.uploadBlob(blob, {encoding})
15 }
16
17 if (typeof input === 'string' && input.startsWith('/')) {
18 const blob = await asBlob(`file://${input}`)
19 return agent.uploadBlob(blob, {encoding})
20 }
21
22 if (typeof input === 'string' && input.startsWith('data:')) {
23 const blob = await fetch(input).then(r => r.blob())
24 return agent.uploadBlob(blob, {encoding})
25 }
26
27 if (input instanceof Blob) {
28 return agent.uploadBlob(input, {encoding})
29 }
30
31 throw new TypeError(`Invalid uploadBlob input: ${typeof input}`)
32}
33
34async function asBlob(uri: string): Promise<Blob> {
35 return withSafeFile(uri, async safeUri => {
36 // Note
37 // Android does not support `fetch()` on `file://` URIs. for this reason, we
38 // use XMLHttpRequest instead of simply calling:
39
40 // return fetch(safeUri.replace('file:///', 'file:/')).then(r => r.blob())
41
42 return await new Promise((resolve, reject) => {
43 const xhr = new XMLHttpRequest()
44 xhr.onload = () => resolve(xhr.response)
45 xhr.onerror = () => reject(new Error('Failed to load blob'))
46 xhr.responseType = 'blob'
47 xhr.open('GET', safeUri, true)
48 xhr.send(null)
49 })
50 })
51}
52
53// HACK
54// React native has a bug that inflates the size of jpegs on upload
55// we get around that by renaming the file ext to .bin
56// see https://github.com/facebook/react-native/issues/27099
57// -prf
58async function withSafeFile<T>(
59 uri: string,
60 fn: (path: string) => Promise<T>,
61): Promise<T> {
62 if (uri.endsWith('.jpeg') || uri.endsWith('.jpg')) {
63 // Since we don't "own" the file, we should avoid renaming or modifying it.
64 // Instead, let's copy it to a temporary file and use that (then remove the
65 // temporary file).
66 const newPath = uri.replace(/\.jpe?g$/, '.bin')
67 try {
68 await RNFS.copyFile(uri, newPath)
69 } catch {
70 // Failed to copy the file, just use the original
71 return await fn(uri)
72 }
73 try {
74 return await fn(newPath)
75 } finally {
76 // Remove the temporary file
77 await RNFS.unlink(newPath)
78 }
79 } else {
80 return fn(uri)
81 }
82}