fix(doodstream): fail fast instead of uploading into a dead hardcoded node
Real root cause from the 3.3.24 diagnostics: the failing upload used CDN "tr1128ve.cloudatacdn.com/upload/01" — character-for-character the hardcoded last-resort fallback in _getUploadServer(). The CDN form came back with only op=upload_result and NO fn/NO st, i.e. the bytes went into a stale node that returns an empty form. So _getUploadServer can no longer extract the current upload server (Doodstream likely changed the upload_server response/format) and silently fell back to a dead node — wasting ~90s/95MB per attempt. - Remove the silent hardcoded-node fallback; throw a clear error when discovery fails so the upload fails instantly instead of 90s later with a cryptic msg. - Embed the raw upload_server response (status, content-type, body) and upload-page URL hints in the error AND debug log, to pin the format change. - Tests: getUploadServer JSON path, srv_url HTML fallback, and the no-silent- fallback throw (asserts the hardcoded node never leaks into the error). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
9794efde46
commit
52751df735
@ -198,6 +198,8 @@ class DoodstreamUploader {
|
|||||||
// Use the standard upload server endpoint
|
// Use the standard upload server endpoint
|
||||||
const res = await this._fetch(BASE_URL + '/?op=upload_server');
|
const res = await this._fetch(BASE_URL + '/?op=upload_server');
|
||||||
const text = await res.text();
|
const text = await res.text();
|
||||||
|
const ctype = (res.headers && res.headers.get) ? (res.headers.get('content-type') || '') : '';
|
||||||
|
_debugLog(`upload_server: status=${res.status} ctype=${ctype} body(800)=${(text || '').slice(0, 800)}`);
|
||||||
let json;
|
let json;
|
||||||
try { json = JSON.parse(text); } catch { json = null; }
|
try { json = JSON.parse(text); } catch { json = null; }
|
||||||
|
|
||||||
@ -211,8 +213,18 @@ class DoodstreamUploader {
|
|||||||
const srvMatch = html.match(/srv_url['":\s]+['"]?(https?:\/\/[^'">\s]+)['"]?/i);
|
const srvMatch = html.match(/srv_url['":\s]+['"]?(https?:\/\/[^'">\s]+)['"]?/i);
|
||||||
if (srvMatch) return srvMatch[1];
|
if (srvMatch) return srvMatch[1];
|
||||||
|
|
||||||
// Last resort fallback
|
// No upload server could be extracted. We MUST NOT silently fall back to a
|
||||||
return 'https://tr1128ve.cloudatacdn.com/upload/01';
|
// hardcoded node: that node is stale and accepts the bytes but returns an
|
||||||
|
// empty form (no filecode) — so the user wastes ~90s uploading 95 MB into a
|
||||||
|
// dead end and gets a cryptic "kein Filecode" 90s later. Fail fast and put
|
||||||
|
// the raw responses in the error so the real format change is diagnosable.
|
||||||
|
const urlHints = (html.match(/https?:\/\/[^'">\s]+/g) || []).slice(0, 4).join(' , ');
|
||||||
|
_debugLog(`upload_server: NO SERVER. upload-page html(2000)=${(html || '').slice(0, 2000)}`);
|
||||||
|
throw new Error(
|
||||||
|
`Doodstream: konnte Upload-Server nicht ermitteln (Endpoint geaendert?). ` +
|
||||||
|
`op=upload_server status=${res.status} ctype=${ctype} body=${(text || '').slice(0, 300)} ` +
|
||||||
|
`| upload-page URL-Treffer: ${urlHints || 'keine'}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -63,3 +63,39 @@ test('happy path: link in result page wins', async () => {
|
|||||||
const res = await up._parseUploadResponse(cdnForm({ fn: 'jjsuhr931ds9', st: 'OK' }));
|
const res = await up._parseUploadResponse(cdnForm({ fn: 'jjsuhr931ds9', st: 'OK' }));
|
||||||
assert.equal(res.file_code, 'jjsuhr931ds9');
|
assert.equal(res.file_code, 'jjsuhr931ds9');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- _getUploadServer: discovery must never fall back to a hardcoded node ---
|
||||||
|
function fakeRes(body, { status = 200, ctype = 'text/html' } = {}) {
|
||||||
|
return { status, headers: { get: (h) => (h.toLowerCase() === 'content-type' ? ctype : null) }, text: async () => body };
|
||||||
|
}
|
||||||
|
|
||||||
|
test('getUploadServer: returns JSON result when present', async () => {
|
||||||
|
const up = new DoodstreamUploader();
|
||||||
|
up._fetch = async (url) => {
|
||||||
|
assert.match(url, /op=upload_server/);
|
||||||
|
return fakeRes(JSON.stringify({ result: 'https://node42.cloudatacdn.com/upload/01' }), { ctype: 'application/json' });
|
||||||
|
};
|
||||||
|
assert.equal(await up._getUploadServer(), 'https://node42.cloudatacdn.com/upload/01');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getUploadServer: falls back to srv_url in upload-page HTML', async () => {
|
||||||
|
const up = new DoodstreamUploader();
|
||||||
|
up._fetch = async (url) => {
|
||||||
|
if (/op=upload_server/.test(url)) return fakeRes('<html>not json</html>');
|
||||||
|
return fakeRes('<script>var srv_url: "https://node7.cloudatacdn.com/upload/01";</script>');
|
||||||
|
};
|
||||||
|
assert.equal(await up._getUploadServer(), 'https://node7.cloudatacdn.com/upload/01');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getUploadServer: throws (no silent dead fallback) when discovery fails', async () => {
|
||||||
|
const up = new DoodstreamUploader();
|
||||||
|
up._fetch = async () => fakeRes('<html><body>login required</body></html>', { status: 200 });
|
||||||
|
await assert.rejects(
|
||||||
|
() => up._getUploadServer(),
|
||||||
|
(err) => {
|
||||||
|
assert.match(err.message, /konnte Upload-Server nicht ermitteln/);
|
||||||
|
assert.doesNotMatch(err.message, /tr1128ve\.cloudatacdn\.com/); // never the hardcoded node
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user