mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at samuel/exp-cli 84 lines 2.6 kB view raw
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}