| 112 | // ── HTTP helpers ───────────────────────────────────────────────────────────── |
| 113 | |
| 114 | function downloadFile(url, dest) { |
| 115 | return new Promise((resolve, reject) => { |
| 116 | const file = createWriteStream(dest); |
| 117 | function doGet(u) { |
| 118 | get(u, { headers: { "User-Agent": "agent-canvas-build" } }, (res) => { |
| 119 | if (res.statusCode === 301 || res.statusCode === 302) { |
| 120 | return doGet(res.headers.location); |
| 121 | } |
| 122 | if (res.statusCode !== 200) { |
| 123 | file.destroy(); |
| 124 | return reject(new Error(`GET ${u} → HTTP ${res.statusCode}`)); |
| 125 | } |
| 126 | res.pipe(file); |
| 127 | file.on("finish", () => file.close(resolve)); |
| 128 | file.on("error", reject); |
| 129 | res.on("error", reject); |
| 130 | }).on("error", (err) => { |
| 131 | file.destroy(); |
| 132 | reject(err); |
| 133 | }); |
| 134 | } |
| 135 | doGet(url); |
| 136 | }); |
| 137 | } |
| 138 | |
| 139 | // ── Extraction ─────────────────────────────────────────────────────────────── |
| 140 | |