handy online tools for AT Protocol boat.kelinci.net
atproto bluesky atcute typescript solidjs

refactor: make blob export work better fixes https://github.com/mary-ext/boat/issues/5

mary.my.id d1c0b1f7 3fd3c309

verified
Changed files
+42 -25
src
views
+42 -25
src/views/blob/blob-export.tsx
··· 1 1 import { FileSystemWritableFileStream, showSaveFilePicker } from 'native-file-system-adapter'; 2 2 import { createSignal } from 'solid-js'; 3 3 4 - import { simpleFetchHandler, XRPC, XRPCError } from '@atcute/client'; 4 + import { Client, ClientResponseError, ok, simpleFetchHandler } from '@atcute/client'; 5 5 import { type AtprotoDid, getPdsEndpoint, isAtprotoDid, isHandle } from '@atcute/identity'; 6 6 import { writeTarEntry } from '@mary/tar'; 7 7 ··· 65 65 service = endpoint; 66 66 } 67 67 68 - const rpc = new XRPC({ handler: simpleFetchHandler({ service }) }); 68 + // const rpc = new XRPC({ handler: simpleFetchHandler({ service }) }); 69 + const client = new Client({ handler: simpleFetchHandler({ service }) }); 69 70 70 71 // Grab a list of blobs 71 72 let blobs: string[] = []; ··· 74 75 75 76 let cursor: string | undefined; 76 77 do { 77 - const { data } = await rpc.get('com.atproto.sync.listBlobs', { 78 - signal, 79 - params: { did, cursor, limit: 1_000 }, 80 - }); 78 + const data = await ok( 79 + client.get('com.atproto.sync.listBlobs', { 80 + signal, 81 + params: { did, cursor, limit: 1_000 }, 82 + }), 83 + ); 81 84 82 85 cursor = data.cursor; 83 86 blobs = blobs.concat(data.cids); ··· 151 154 attempts++; 152 155 153 156 try { 154 - const { data } = await rpc.get('com.atproto.sync.getBlob', { 157 + const response = await client.get('com.atproto.sync.getBlob', { 155 158 signal, 159 + as: 'bytes', 156 160 params: { did, cid }, 157 161 }); 158 162 159 - return data; 160 - } catch (err) { 161 - if (attempts > 3) { 162 - throw err; 163 + if (response.ok) { 164 + return response.data; 163 165 } 164 166 165 - if (err instanceof XRPCError) { 166 - if (err.status === 400) { 167 - if (err.message === 'Blob not found') { 168 - console.warn(`Blob ${cid} not found`); 169 - return; 170 - } 171 - } else if (err.status === 429) { 172 - const reset = err.headers?.['ratelimit-reset']; 167 + if (response.status === 400) { 168 + // If the PDS says it can't find the blob, stop right here. 169 + if (response.data.message === 'Blob not found') { 170 + logger.warn(`Blob ${cid} not found`); 171 + return undefined; 172 + } 173 + } else if (response.status === 429) { 174 + // Not exposed by CORS, hoping that someday it will 175 + const reset = response.headers.get('ratelimit-reset'); 173 176 174 - if (reset !== undefined) { 175 - logger.warn(`Ratelimit exceeded when downloading ${cid}, waiting`); 177 + logger.warn(`Ratelimit exceeded when downloading ${cid}, waiting`); 176 178 177 - const refreshAt = +reset * 1_000; 178 - const delta = refreshAt - Date.now(); 179 + if (reset !== null) { 180 + const refreshAt = +reset * 1_000; 181 + const delta = refreshAt - Date.now(); 179 182 180 - await sleep(delta); 181 - } 183 + await sleep(delta); 184 + } else { 185 + await sleep(10_000); 182 186 } 183 187 } 188 + 189 + if (attempts < 3) { 190 + continue; 191 + } 192 + 193 + throw new ClientResponseError(response); 194 + } catch (err) { 195 + // Network errors, etc 196 + if (attempts < 3) { 197 + continue; 198 + } 199 + 200 + throw err; 184 201 } 185 202 } 186 203 };