forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {type AppBskyVideoDefs} from '@atproto/api'
2import {type BskyAgent} from '@atproto/api'
3import {type I18n} from '@lingui/core'
4import {msg} from '@lingui/macro'
5import {nanoid} from 'nanoid/non-secure'
6
7import {AbortError} from '#/lib/async/cancelable'
8import {ServerError} from '#/lib/media/video/errors'
9import {type CompressedVideo} from '#/lib/media/video/types'
10import {getServiceAuthToken, getVideoUploadLimits} from './upload.shared'
11import {createVideoEndpointUrl, mimeToExt} from './util'
12
13export async function uploadVideo({
14 video,
15 agent,
16 did,
17 setProgress,
18 signal,
19 _,
20}: {
21 video: CompressedVideo
22 agent: BskyAgent
23 did: string
24 setProgress: (progress: number) => void
25 signal: AbortSignal
26 _: I18n['_']
27}) {
28 if (signal.aborted) {
29 throw new AbortError()
30 }
31 await getVideoUploadLimits(agent, _)
32
33 const uri = createVideoEndpointUrl('/xrpc/app.bsky.video.uploadVideo', {
34 did,
35 name: `${nanoid(12)}.${mimeToExt(video.mimeType)}`,
36 })
37
38 let bytes = video.bytes
39 if (!bytes) {
40 if (signal.aborted) {
41 throw new AbortError()
42 }
43 bytes = await fetch(video.uri).then(res => res.arrayBuffer())
44 }
45
46 if (signal.aborted) {
47 throw new AbortError()
48 }
49 const token = await getServiceAuthToken({
50 agent,
51 lxm: 'com.atproto.repo.uploadBlob',
52 exp: Date.now() / 1000 + 60 * 30, // 30 minutes
53 })
54
55 if (signal.aborted) {
56 throw new AbortError()
57 }
58 const xhr = new XMLHttpRequest()
59 const res = await new Promise<AppBskyVideoDefs.JobStatus>(
60 (resolve, reject) => {
61 xhr.upload.addEventListener('progress', e => {
62 const progress = e.loaded / e.total
63 setProgress(progress)
64 })
65 xhr.onloadend = () => {
66 if (signal.aborted) {
67 reject(new AbortError())
68 } else if (xhr.readyState === 4) {
69 const uploadRes = JSON.parse(
70 xhr.responseText,
71 ) as AppBskyVideoDefs.JobStatus
72 resolve(uploadRes)
73 } else {
74 reject(new ServerError(_(msg`Failed to upload video`)))
75 }
76 }
77 xhr.onerror = () => {
78 reject(new ServerError(_(msg`Failed to upload video`)))
79 }
80 xhr.open('POST', uri)
81 xhr.setRequestHeader('Content-Type', video.mimeType)
82 xhr.setRequestHeader('Authorization', `Bearer ${token}`)
83 xhr.send(bytes)
84 },
85 )
86
87 if (!res.jobId) {
88 throw new ServerError(res.error || _(msg`Failed to upload video`))
89 }
90
91 if (signal.aborted) {
92 throw new AbortError()
93 }
94 return res
95}