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