import {useCallback, useState} from 'react' import {View} from 'react-native' import {Trans, useLingui} from '@lingui/react/macro' import {saveBytesToDisk} from '#/lib/media/manip' import {logger} from '#/logger' import {useChatClient, usePdsClient, useSession} from '#/state/session' import {atoms as a, useTheme, web} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import {Download_Stroke2_Corner0_Rounded as DownloadIcon} from '#/components/icons/Download' import {InlineLinkText} from '#/components/Link' import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {chat, com} from '#/lexicons' export function ExportCarDialog({ control, }: { control: Dialog.DialogControlProps }) { const {t: l} = useLingui() const t = useTheme() const {currentAccount} = useSession() const pdsClient = usePdsClient() const chatClient = useChatClient() const [loading, setLoading] = useState<'repo' | 'chat' | false>(false) const download = useCallback(async () => { if (!currentAccount) { return // shouldn't ever happen } try { setLoading('repo') const did = currentAccount.did const data = await pdsClient.call(com.atproto.sync.getRepo, {did}) /* * getRepo declares `application/vnd.ipld.car`, so lex-client hands back * the raw bytes unparsed and does not surface the response content-type. * The old code already fell back to this same constant when the header was * absent, and the endpoint always returns CAR. */ const saveRes = await saveBytesToDisk( 'repo.car', data, 'application/vnd.ipld.car', ) if (saveRes) { Toast.show(l`File saved successfully!`) } } catch (e) { logger.error('Error occurred while downloading CAR file', {message: e}) Toast.show(l`Error occurred while saving file`, {type: 'error'}) } finally { setLoading(false) } }, [l, currentAccount, pdsClient]) const downloadChatData = useCallback(async () => { if (!currentAccount) { return } try { setLoading('chat') /* * lex-client only JSON-parses a response when the declared output encoding * is `application/json`; this endpoint declares `application/jsonl`, so it * returns the raw bytes. That removes the reason for the old low-level * fetchHandler workaround, and the chat client emits the proxy header * itself, so the per-call DM headers go away too. */ const data = await chatClient.call(chat.bsky.actor.exportAccountData) const saveRes = await saveBytesToDisk( 'chat.jsonl', data, 'application/jsonl', ) if (saveRes) { Toast.show(l`File saved successfully!`) } } catch (e) { logger.error('Error occurred while downloading chat data', {message: e}) Toast.show(l`Error occurred while saving file`, {type: 'error'}) } finally { setLoading(false) } }, [l, currentAccount, chatClient]) return ( Export my profile data Your account repository, containing all public data records, can be downloaded as a "CAR" file. This file does not include media embeds, such as images, or your private data, which must be fetched separately. Export my chat data You can also download your chat data as a "JSONL" file. This file only includes chat messages that you have sent and does not include chat messages that you have received. This feature is in beta. You can read more about repository exports in{' '} this blogpost . ) }