forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {useCallback, useState} from 'react'
2import {View} from 'react-native'
3import {msg} from '@lingui/core/macro'
4import {useLingui} from '@lingui/react'
5import {Trans} from '@lingui/react/macro'
6
7import {DM_SERVICE_HEADERS} from '#/lib/constants'
8import {saveBytesToDisk} from '#/lib/media/manip'
9import {logger} from '#/logger'
10import {useAgent} from '#/state/session'
11import {atoms as a, useTheme, web} from '#/alf'
12import {Button, ButtonIcon, ButtonText} from '#/components/Button'
13import * as Dialog from '#/components/Dialog'
14import {Download_Stroke2_Corner0_Rounded as DownloadIcon} from '#/components/icons/Download'
15import {InlineLinkText} from '#/components/Link'
16import {Loader} from '#/components/Loader'
17import * as Toast from '#/components/Toast'
18import {Text} from '#/components/Typography'
19
20export function ExportCarDialog({
21 control,
22}: {
23 control: Dialog.DialogControlProps
24}) {
25 const {_} = useLingui()
26 const t = useTheme()
27 const agent = useAgent()
28 const [loading, setLoading] = useState<'repo' | 'chat' | false>(false)
29
30 const download = useCallback(async () => {
31 if (!agent.session) {
32 return // shouldnt ever happen
33 }
34 try {
35 setLoading('repo')
36 const did = agent.session.did
37 const downloadRes = await agent.com.atproto.sync.getRepo({did})
38 const saveRes = await saveBytesToDisk(
39 'repo.car',
40 downloadRes.data,
41 downloadRes.headers['content-type'] || 'application/vnd.ipld.car',
42 )
43
44 if (saveRes) {
45 Toast.show(_(msg`File saved successfully!`))
46 }
47 } catch (e) {
48 logger.error('Error occurred while downloading CAR file', {message: e})
49 Toast.show(_(msg`Error occurred while saving file`), {type: 'error'})
50 } finally {
51 setLoading(false)
52 control.close()
53 }
54 }, [_, control, agent])
55
56 const downloadChatData = useCallback(async () => {
57 if (!agent.session) {
58 return
59 }
60 try {
61 setLoading('chat')
62 // Using raw fetch because the XRPC client incorrectly tries to JSON-parse
63 // application/jsonl responses (substring match on application/json).
64 const res = await agent.sessionManager.fetchHandler(
65 '/xrpc/chat.bsky.actor.exportAccountData',
66 {headers: DM_SERVICE_HEADERS},
67 )
68 if (!res.ok) {
69 throw new Error(`HTTP ${res.status}`)
70 }
71 const data = new Uint8Array(await res.arrayBuffer())
72 const saveRes = await saveBytesToDisk(
73 'chat.jsonl',
74 data,
75 res.headers.get('content-type') || 'application/jsonl',
76 )
77
78 if (saveRes) {
79 Toast.show(_(msg`File saved successfully!`))
80 }
81 } catch (e) {
82 logger.error('Error occurred while downloading chat data', {message: e})
83 Toast.show(_(msg`Error occurred while saving file`), {type: 'error'})
84 } finally {
85 setLoading(false)
86 control.close()
87 }
88 }, [_, control, agent])
89
90 return (
91 <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
92 <Dialog.Handle />
93 <Dialog.ScrollableInner
94 accessibilityDescribedBy="dialog-description"
95 accessibilityLabelledBy="dialog-title"
96 style={web({maxWidth: 500})}>
97 <View style={[a.relative, a.gap_lg, a.w_full]}>
98 <Text nativeID="dialog-title" style={[a.text_2xl, a.font_bold]}>
99 <Trans>Export My Data</Trans>
100 </Text>
101 <Text
102 nativeID="dialog-description"
103 style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_high]}>
104 <Trans>
105 Your account repository, containing all public data records, can
106 be downloaded as a "CAR" file. This file does not include media
107 embeds, such as images, or your private data, which must be
108 fetched separately.
109 </Trans>
110 </Text>
111
112 <Button
113 color="primary"
114 size="large"
115 label={_(msg`Download CAR file`)}
116 disabled={!!loading}
117 onPress={download}>
118 <ButtonIcon icon={DownloadIcon} />
119 <ButtonText>
120 <Trans>Download CAR file</Trans>
121 </ButtonText>
122 {loading === 'repo' && <ButtonIcon icon={Loader} />}
123 </Button>
124
125 <Text style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_high]}>
126 <Trans>
127 You can also download your chat data as a "JSONL" file. This file
128 only includes chat messages that you have sent and does not
129 include chat messages that you have received.
130 </Trans>
131 </Text>
132
133 <Button
134 color="secondary"
135 size="large"
136 label={_(msg`Download chat data`)}
137 disabled={!!loading}
138 onPress={downloadChatData}>
139 <ButtonIcon icon={DownloadIcon} />
140 <ButtonText>
141 <Trans>Download chat data</Trans>
142 </ButtonText>
143 {loading === 'chat' && <ButtonIcon icon={Loader} />}
144 </Button>
145
146 <Text
147 style={[
148 t.atoms.text_contrast_medium,
149 a.text_sm,
150 a.leading_snug,
151 a.flex_1,
152 ]}>
153 <Trans>
154 This feature is in beta. You can read more about repository
155 exports in{' '}
156 <InlineLinkText
157 label={_(msg`View blogpost for more details`)}
158 to="https://docs.bsky.app/blog/repo-export"
159 style={[a.text_sm]}>
160 this blogpost
161 </InlineLinkText>
162 .
163 </Trans>
164 </Text>
165 </View>
166 <Dialog.Close />
167 </Dialog.ScrollableInner>
168 </Dialog.Outer>
169 )
170}