this repo has no description
1import type { AtprotoClient } from "./atproto-client"; 2import type { MigrationProgress } from "./types"; 3 4export interface BlobMigrationResult { 5 migrated: number; 6 failed: string[]; 7 total: number; 8 sourceUnreachable: boolean; 9} 10 11export async function migrateBlobs( 12 localClient: AtprotoClient, 13 sourceClient: AtprotoClient | null, 14 userDid: string, 15 onProgress: (update: Partial<MigrationProgress>) => void, 16): Promise<BlobMigrationResult> { 17 const missingBlobs: string[] = []; 18 let cursor: string | undefined; 19 20 console.log("[blob-migration] Starting blob migration for", userDid); 21 console.log( 22 "[blob-migration] Source client:", 23 sourceClient ? "available" : "NOT AVAILABLE", 24 ); 25 26 onProgress({ currentOperation: "Checking for missing blobs..." }); 27 28 do { 29 const { blobs, cursor: nextCursor } = await localClient.listMissingBlobs( 30 cursor, 31 100, 32 ); 33 console.log( 34 "[blob-migration] listMissingBlobs returned", 35 blobs.length, 36 "blobs, cursor:", 37 nextCursor, 38 ); 39 for (const blob of blobs) { 40 missingBlobs.push(blob.cid); 41 } 42 cursor = nextCursor; 43 } while (cursor); 44 45 console.log("[blob-migration] Total missing blobs:", missingBlobs.length); 46 onProgress({ blobsTotal: missingBlobs.length }); 47 48 if (missingBlobs.length === 0) { 49 console.log("[blob-migration] No blobs to migrate"); 50 onProgress({ currentOperation: "No blobs to migrate" }); 51 return { migrated: 0, failed: [], total: 0, sourceUnreachable: false }; 52 } 53 54 if (!sourceClient) { 55 console.warn( 56 "[blob-migration] No source client available, cannot fetch blobs", 57 ); 58 onProgress({ 59 currentOperation: 60 `${missingBlobs.length} media files missing. No source PDS URL available - your old server may have shut down. Posts will work, but some images/media may be unavailable.`, 61 }); 62 return { 63 migrated: 0, 64 failed: missingBlobs, 65 total: missingBlobs.length, 66 sourceUnreachable: true, 67 }; 68 } 69 70 onProgress({ currentOperation: `Migrating ${missingBlobs.length} blobs...` }); 71 72 let migrated = 0; 73 const failed: string[] = []; 74 let sourceUnreachable = false; 75 76 for (const cid of missingBlobs) { 77 if (sourceUnreachable) { 78 failed.push(cid); 79 continue; 80 } 81 82 try { 83 onProgress({ 84 currentOperation: `Migrating blob ${ 85 migrated + 1 86 }/${missingBlobs.length}...`, 87 }); 88 89 console.log("[blob-migration] Fetching blob", cid, "from source"); 90 const blobData = await sourceClient.getBlob(userDid, cid); 91 console.log( 92 "[blob-migration] Got blob", 93 cid, 94 "size:", 95 blobData.byteLength, 96 ); 97 await localClient.uploadBlob(blobData, "application/octet-stream"); 98 console.log("[blob-migration] Uploaded blob", cid); 99 migrated++; 100 onProgress({ blobsMigrated: migrated }); 101 } catch (e) { 102 const errorMessage = (e as Error).message || String(e); 103 console.error( 104 "[blob-migration] Failed to migrate blob", 105 cid, 106 ":", 107 errorMessage, 108 ); 109 110 const isNetworkError = errorMessage.includes("fetch") || 111 errorMessage.includes("network") || 112 errorMessage.includes("CORS") || 113 errorMessage.includes("Failed to fetch") || 114 errorMessage.includes("NetworkError") || 115 errorMessage.includes("blocked by CORS"); 116 117 if (isNetworkError) { 118 sourceUnreachable = true; 119 console.warn( 120 "[blob-migration] Source appears unreachable (likely CORS or network issue), skipping remaining blobs", 121 ); 122 const remaining = missingBlobs.length - migrated - 1; 123 if (migrated > 0) { 124 onProgress({ 125 currentOperation: 126 `Source PDS unreachable (browser security restriction). ${migrated} media files migrated successfully. ${ 127 remaining + 1 128 } could not be fetched - these may need to be re-uploaded.`, 129 }); 130 } else { 131 onProgress({ 132 currentOperation: 133 `Cannot reach source PDS (browser security restriction). This commonly happens when the old server has shut down or doesn't allow cross-origin requests. Your posts will work, but ${missingBlobs.length} media files couldn't be recovered.`, 134 }); 135 } 136 } 137 failed.push(cid); 138 } 139 } 140 141 if (migrated === missingBlobs.length) { 142 onProgress({ 143 currentOperation: `All ${migrated} blobs migrated successfully`, 144 }); 145 } else if (migrated > 0) { 146 onProgress({ 147 currentOperation: 148 `${migrated}/${missingBlobs.length} blobs migrated. ${failed.length} failed.`, 149 }); 150 } else { 151 onProgress({ 152 currentOperation: `Could not migrate blobs (${failed.length} missing)`, 153 }); 154 } 155 156 return { migrated, failed, total: missingBlobs.length, sourceUnreachable }; 157}