about summary refs log tree commit diff
path: root/src/lib/media/video/upload.web.ts
diff options
context:
space:
mode:
authordan <dan.abramov@gmail.com>2024-10-03 14:57:48 +0900
committerGitHub <noreply@github.com>2024-10-03 14:57:48 +0900
commit2aa365b5b6fed920d17d307252a7af7c52b95855 (patch)
treeb8083438359e63367e983b461f5835cf8b75aa8c /src/lib/media/video/upload.web.ts
parent03704e2b48e6cdc348ce7277f2bcae0c61519d1e (diff)
downloadvoidsky-2aa365b5b6fed920d17d307252a7af7c52b95855.tar.zst
Rename some files and variables (#5587)
* Move composer reducers together

* videoUploadState -> videoState

* Inline videoDispatch
Diffstat (limited to 'src/lib/media/video/upload.web.ts')
-rw-r--r--src/lib/media/video/upload.web.ts95
1 files changed, 95 insertions, 0 deletions
diff --git a/src/lib/media/video/upload.web.ts b/src/lib/media/video/upload.web.ts
new file mode 100644
index 000000000..ec65f96c9
--- /dev/null
+++ b/src/lib/media/video/upload.web.ts
@@ -0,0 +1,95 @@
+import {AppBskyVideoDefs} from '@atproto/api'
+import {BskyAgent} from '@atproto/api'
+import {I18n} from '@lingui/core'
+import {msg} from '@lingui/macro'
+import {nanoid} from 'nanoid/non-secure'
+
+import {AbortError} from '#/lib/async/cancelable'
+import {ServerError} from '#/lib/media/video/errors'
+import {CompressedVideo} from '#/lib/media/video/types'
+import {createVideoEndpointUrl, mimeToExt} from './util'
+import {getServiceAuthToken, getVideoUploadLimits} from './upload.shared'
+
+export async function uploadVideo({
+  video,
+  agent,
+  did,
+  setProgress,
+  signal,
+  _,
+}: {
+  video: CompressedVideo
+  agent: BskyAgent
+  did: string
+  setProgress: (progress: number) => void
+  signal: AbortSignal
+  _: I18n['_']
+}) {
+  if (signal.aborted) {
+    throw new AbortError()
+  }
+  await getVideoUploadLimits(agent, _)
+
+  const uri = createVideoEndpointUrl('/xrpc/app.bsky.video.uploadVideo', {
+    did,
+    name: `${nanoid(12)}.${mimeToExt(video.mimeType)}`,
+  })
+
+  let bytes = video.bytes
+  if (!bytes) {
+    if (signal.aborted) {
+      throw new AbortError()
+    }
+    bytes = await fetch(video.uri).then(res => res.arrayBuffer())
+  }
+
+  if (signal.aborted) {
+    throw new AbortError()
+  }
+  const token = await getServiceAuthToken({
+    agent,
+    lxm: 'com.atproto.repo.uploadBlob',
+    exp: Date.now() / 1000 + 60 * 30, // 30 minutes
+  })
+
+  if (signal.aborted) {
+    throw new AbortError()
+  }
+  const xhr = new XMLHttpRequest()
+  const res = await new Promise<AppBskyVideoDefs.JobStatus>(
+    (resolve, reject) => {
+      xhr.upload.addEventListener('progress', e => {
+        const progress = e.loaded / e.total
+        setProgress(progress)
+      })
+      xhr.onloadend = () => {
+        if (signal.aborted) {
+          reject(new AbortError())
+        } else if (xhr.readyState === 4) {
+          const uploadRes = JSON.parse(
+            xhr.responseText,
+          ) as AppBskyVideoDefs.JobStatus
+          resolve(uploadRes)
+        } else {
+          reject(new ServerError(_(msg`Failed to upload video`)))
+        }
+      }
+      xhr.onerror = () => {
+        reject(new ServerError(_(msg`Failed to upload video`)))
+      }
+      xhr.open('POST', uri)
+      xhr.setRequestHeader('Content-Type', video.mimeType)
+      xhr.setRequestHeader('Authorization', `Bearer ${token}`)
+      xhr.send(bytes)
+    },
+  )
+
+  if (!res.jobId) {
+    throw new ServerError(res.error || _(msg`Failed to upload video`))
+  }
+
+  if (signal.aborted) {
+    throw new AbortError()
+  }
+  return res
+}