this repo has no description
1import type { AtprotoClient } from "./atproto-client.ts"; 2import type { MigrationProgress } from "./types.ts"; 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 missingBlobs.push(...blobs.map((blob) => blob.cid)); 40 cursor = nextCursor; 41 } while (cursor); 42 43 console.log("[blob-migration] Total missing blobs:", missingBlobs.length); 44 onProgress({ blobsTotal: missingBlobs.length }); 45 46 if (missingBlobs.length === 0) { 47 console.log("[blob-migration] No blobs to migrate"); 48 onProgress({ currentOperation: "No blobs to migrate" }); 49 return { migrated: 0, failed: [], total: 0, sourceUnreachable: false }; 50 } 51 52 if (!sourceClient) { 53 console.warn( 54 "[blob-migration] No source client available, cannot fetch blobs", 55 ); 56 onProgress({ 57 currentOperation: 58 `${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.`, 59 }); 60 return { 61 migrated: 0, 62 failed: missingBlobs, 63 total: missingBlobs.length, 64 sourceUnreachable: true, 65 }; 66 } 67 68 onProgress({ currentOperation: `Migrating ${missingBlobs.length} blobs...` }); 69 70 let migrated = 0; 71 const failed: string[] = []; 72 let sourceUnreachable = false; 73 74 for (const cid of missingBlobs) { 75 if (sourceUnreachable) { 76 failed.push(cid); 77 continue; 78 } 79 80 try { 81 onProgress({ 82 currentOperation: `Migrating blob ${ 83 migrated + 1 84 }/${missingBlobs.length}...`, 85 }); 86 87 console.log("[blob-migration] Fetching blob", cid, "from source"); 88 const { data: blobData, contentType } = await sourceClient 89 .getBlobWithContentType(userDid, cid); 90 console.log( 91 "[blob-migration] Got blob", 92 cid, 93 "size:", 94 blobData.byteLength, 95 "contentType:", 96 contentType, 97 ); 98 await localClient.uploadBlob(blobData, contentType); 99 console.log( 100 "[blob-migration] Uploaded blob", 101 cid, 102 "with contentType:", 103 contentType, 104 ); 105 migrated++; 106 onProgress({ blobsMigrated: migrated }); 107 } catch (e) { 108 const errorMessage = (e as Error).message || String(e); 109 console.error( 110 "[blob-migration] Failed to migrate blob", 111 cid, 112 ":", 113 errorMessage, 114 ); 115 116 const isNetworkError = errorMessage.includes("fetch") || 117 errorMessage.includes("network") || 118 errorMessage.includes("CORS") || 119 errorMessage.includes("Failed to fetch") || 120 errorMessage.includes("NetworkError") || 121 errorMessage.includes("blocked by CORS"); 122 123 if (isNetworkError) { 124 sourceUnreachable = true; 125 console.warn( 126 "[blob-migration] Source appears unreachable (likely CORS or network issue), skipping remaining blobs", 127 ); 128 const remaining = missingBlobs.length - migrated - 1; 129 if (migrated > 0) { 130 onProgress({ 131 currentOperation: 132 `Source PDS unreachable (browser security restriction). ${migrated} media files migrated successfully. ${ 133 remaining + 1 134 } could not be fetched - these may need to be re-uploaded.`, 135 }); 136 } else { 137 onProgress({ 138 currentOperation: 139 `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.`, 140 }); 141 } 142 } 143 failed.push(cid); 144 } 145 } 146 147 if (migrated === missingBlobs.length) { 148 onProgress({ 149 currentOperation: `All ${migrated} blobs migrated successfully`, 150 }); 151 } else if (migrated > 0) { 152 onProgress({ 153 currentOperation: 154 `${migrated}/${missingBlobs.length} blobs migrated. ${failed.length} failed.`, 155 }); 156 } else { 157 onProgress({ 158 currentOperation: `Could not migrate blobs (${failed.length} missing)`, 159 }); 160 } 161 162 return { migrated, failed, total: missingBlobs.length, sourceUnreachable }; 163}