forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {createUploadTask, FileSystemUploadType} from 'expo-file-system/legacy'
2import {type AppBskyVideoDefs, type BskyAgent} from '@atproto/api'
3import {type I18n} from '@lingui/core'
4import {msg} from '@lingui/core/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 if (signal.aborted) {
39 throw new AbortError()
40 }
41 const token = await getServiceAuthToken({
42 agent,
43 lxm: 'com.atproto.repo.uploadBlob',
44 exp: Date.now() / 1000 + 60 * 30, // 30 minutes
45 })
46 const uploadTask = createUploadTask(
47 uri,
48 video.uri,
49 {
50 headers: {
51 'content-type': video.mimeType,
52 Authorization: `Bearer ${token}`,
53 },
54 httpMethod: 'POST',
55 uploadType: FileSystemUploadType.BINARY_CONTENT,
56 },
57 p => setProgress(p.totalBytesSent / p.totalBytesExpectedToSend),
58 )
59
60 if (signal.aborted) {
61 throw new AbortError()
62 }
63 const res = await uploadTask.uploadAsync()
64
65 if (!res?.body) {
66 throw new Error('No response')
67 }
68
69 const responseBody = JSON.parse(res.body) as AppBskyVideoDefs.JobStatus
70
71 if (!responseBody.jobId) {
72 throw new ServerError(responseBody.error || _(msg`Failed to upload video`))
73 }
74
75 if (signal.aborted) {
76 throw new AbortError()
77 }
78 return responseBody
79}