about summary refs log tree commit diff
path: root/src/lib/api/upload-blob.ts
diff options
context:
space:
mode:
authorHailey <me@haileyok.com>2024-08-12 14:00:15 -0700
committerGitHub <noreply@github.com>2024-08-12 14:00:15 -0700
commit7df2327424e948e54b9731e5ab651e889f38a772 (patch)
treecd6513394de29696124374d1a72bd4cd78cbc1a7 /src/lib/api/upload-blob.ts
parentae883e2df7bc53baca215fba527fe113e71cb5c2 (diff)
downloadvoidsky-7df2327424e948e54b9731e5ab651e889f38a772.tar.zst
Upgrade API, implement XRPC rework (#4857)
Co-authored-by: Matthieu Sieben <matthieu.sieben@gmail.com>
Diffstat (limited to 'src/lib/api/upload-blob.ts')
-rw-r--r--src/lib/api/upload-blob.ts82
1 files changed, 82 insertions, 0 deletions
diff --git a/src/lib/api/upload-blob.ts b/src/lib/api/upload-blob.ts
new file mode 100644
index 000000000..0814d5185
--- /dev/null
+++ b/src/lib/api/upload-blob.ts
@@ -0,0 +1,82 @@
+import RNFS from 'react-native-fs'
+import {BskyAgent, ComAtprotoRepoUploadBlob} from '@atproto/api'
+
+/**
+ * @param encoding Allows overriding the blob's type
+ */
+export async function uploadBlob(
+  agent: BskyAgent,
+  input: string | Blob,
+  encoding?: string,
+): Promise<ComAtprotoRepoUploadBlob.Response> {
+  if (typeof input === 'string' && input.startsWith('file:')) {
+    const blob = await asBlob(input)
+    return agent.uploadBlob(blob, {encoding})
+  }
+
+  if (typeof input === 'string' && input.startsWith('/')) {
+    const blob = await asBlob(`file://${input}`)
+    return agent.uploadBlob(blob, {encoding})
+  }
+
+  if (typeof input === 'string' && input.startsWith('data:')) {
+    const blob = await fetch(input).then(r => r.blob())
+    return agent.uploadBlob(blob, {encoding})
+  }
+
+  if (input instanceof Blob) {
+    return agent.uploadBlob(input, {encoding})
+  }
+
+  throw new TypeError(`Invalid uploadBlob input: ${typeof input}`)
+}
+
+async function asBlob(uri: string): Promise<Blob> {
+  return withSafeFile(uri, async safeUri => {
+    // Note
+    // Android does not support `fetch()` on `file://` URIs. for this reason, we
+    // use XMLHttpRequest instead of simply calling:
+
+    // return fetch(safeUri.replace('file:///', 'file:/')).then(r => r.blob())
+
+    return await new Promise((resolve, reject) => {
+      const xhr = new XMLHttpRequest()
+      xhr.onload = () => resolve(xhr.response)
+      xhr.onerror = () => reject(new Error('Failed to load blob'))
+      xhr.responseType = 'blob'
+      xhr.open('GET', safeUri, true)
+      xhr.send(null)
+    })
+  })
+}
+
+// HACK
+// React native has a bug that inflates the size of jpegs on upload
+// we get around that by renaming the file ext to .bin
+// see https://github.com/facebook/react-native/issues/27099
+// -prf
+async function withSafeFile<T>(
+  uri: string,
+  fn: (path: string) => Promise<T>,
+): Promise<T> {
+  if (uri.endsWith('.jpeg') || uri.endsWith('.jpg')) {
+    // Since we don't "own" the file, we should avoid renaming or modifying it.
+    // Instead, let's copy it to a temporary file and use that (then remove the
+    // temporary file).
+    const newPath = uri.replace(/\.jpe?g$/, '.bin')
+    try {
+      await RNFS.copyFile(uri, newPath)
+    } catch {
+      // Failed to copy the file, just use the original
+      return await fn(uri)
+    }
+    try {
+      return await fn(newPath)
+    } finally {
+      // Remove the temporary file
+      await RNFS.unlink(newPath)
+    }
+  } else {
+    return fn(uri)
+  }
+}