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