Harden batch-wide recovery identity claims
Scope remote code ownership to normalized hoster and account identities while retaining title-only recovery serialization and canonical Unicode title matching. Mark post-upload ambiguity and duplicate identities as uncertain so retries, account fallback, and later same-title jobs fail closed instead of reporting unsafe success. Acquire recovery title leases before hoster and global semaphores, revalidate failed-account overrides before upload, and clear claim state at batch boundaries. Add deterministic concurrent coverage for same-code rejection, distinct-code parallel success, uncertainty propagation, semaphore fairness, account isolation, Unicode equivalence, and registry lifetime.
This commit is contained in:
+38
-16
@@ -3,6 +3,7 @@ const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const { request } = require('undici');
|
||||
const { createTransportError, sanitizeRemoteText } = require('./hoster-transport-error');
|
||||
const { normalizeRecoveryTitle } = require('./hosters');
|
||||
|
||||
const BASE_URL = 'https://vidmoly.me';
|
||||
const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
|
||||
@@ -275,13 +276,13 @@ class VidmolyUploader {
|
||||
bodyTimeout: UPLOAD_TIMEOUT
|
||||
});
|
||||
} catch (err) {
|
||||
if (signal && signal.aborted) throw err;
|
||||
throw createTransportError('Vidmoly Upload konnte nicht übertragen werden', {
|
||||
const error = signal && signal.aborted ? err : createTransportError('Vidmoly Upload konnte nicht übertragen werden', {
|
||||
phase: 'upload-request',
|
||||
endpoint: targetUrl,
|
||||
retryable: true,
|
||||
transientNetwork: true
|
||||
});
|
||||
throw this._markRemoteCommitUncertain(error);
|
||||
}
|
||||
|
||||
const { body, statusCode, headers } = uploadResponse;
|
||||
@@ -295,17 +296,25 @@ class VidmolyUploader {
|
||||
// Always drain the original body to prevent connection leak
|
||||
try { await body.text(); } catch {}
|
||||
if (location) {
|
||||
const resultRes = await this._fetch(new URL(location, uploadUrl).href);
|
||||
resultHtml = await resultRes.text();
|
||||
try {
|
||||
const resultRes = await this._fetch(new URL(location, uploadUrl).href);
|
||||
resultHtml = await resultRes.text();
|
||||
} catch (err) {
|
||||
throw this._markRemoteCommitUncertain(err);
|
||||
}
|
||||
} else {
|
||||
resultHtml = '';
|
||||
}
|
||||
} else {
|
||||
resultHtml = await body.text();
|
||||
try {
|
||||
resultHtml = await body.text();
|
||||
} catch (err) {
|
||||
throw this._markRemoteCommitUncertain(err);
|
||||
}
|
||||
}
|
||||
|
||||
if (statusCode >= 400) {
|
||||
throw createTransportError('Vidmoly Upload fehlgeschlagen', {
|
||||
const error = createTransportError('Vidmoly Upload fehlgeschlagen', {
|
||||
phase: 'upload-response',
|
||||
endpoint: targetUrl,
|
||||
httpStatus: statusCode,
|
||||
@@ -314,6 +323,7 @@ class VidmolyUploader {
|
||||
retryable: statusCode === 429 || statusCode >= 500,
|
||||
transientNetwork: statusCode >= 500
|
||||
});
|
||||
throw statusCode >= 500 ? this._markRemoteCommitUncertain(error) : error;
|
||||
}
|
||||
|
||||
// Try JSON first. The current transit server returns
|
||||
@@ -355,23 +365,35 @@ class VidmolyUploader {
|
||||
} catch (primaryErr) {
|
||||
if (primaryErr && primaryErr.remoteIdentityClaimed === true) throw primaryErr;
|
||||
if (baselineCodes) {
|
||||
const fallback = await this._resolveUploadedFileFromVmApi(fileName, baselineCodes, signal);
|
||||
if (fallback) return fallback;
|
||||
try {
|
||||
const fallback = await this._resolveUploadedFileFromVmApi(fileName, baselineCodes, signal);
|
||||
if (fallback) return fallback;
|
||||
} catch (err) {
|
||||
throw this._markRemoteCommitUncertain(err);
|
||||
}
|
||||
}
|
||||
if (baselineError) {
|
||||
baselineError.hosterTransient = true;
|
||||
throw baselineError;
|
||||
throw this._markRemoteCommitUncertain(baselineError);
|
||||
}
|
||||
throw primaryErr;
|
||||
throw this._markRemoteCommitUncertain(primaryErr);
|
||||
}
|
||||
}
|
||||
|
||||
_normalizeTitle(value) {
|
||||
return String(value || '')
|
||||
.toLowerCase()
|
||||
.normalize('NFKD')
|
||||
.replace(/\.[a-z0-9]+$/i, '')
|
||||
.replace(/[^a-z0-9]+/g, '');
|
||||
return normalizeRecoveryTitle(value);
|
||||
}
|
||||
|
||||
_markRemoteCommitUncertain(error) {
|
||||
if (this.recoveryClaim && typeof this.recoveryClaim.markUncertain === 'function') {
|
||||
return this.recoveryClaim.markUncertain(error);
|
||||
}
|
||||
const uncertainError = error && typeof error === 'object'
|
||||
? error
|
||||
: new Error('Vidmoly Upload-Ergebnis ist unsicher');
|
||||
uncertainError.remoteCommitUncertain = true;
|
||||
uncertainError.hosterTransient = true;
|
||||
return uncertainError;
|
||||
}
|
||||
|
||||
_buildUrlsFromCode(fileCode, phase = 'upload-result') {
|
||||
@@ -387,7 +409,7 @@ class VidmolyUploader {
|
||||
hosterTransient: true
|
||||
});
|
||||
error.remoteIdentityClaimed = true;
|
||||
throw error;
|
||||
throw this._markRemoteCommitUncertain(error);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user