Preserve pre-start and batch cancellation requests, reject late upload success after cancellation, and wait for cancellation acknowledgements before removing queue entries. Separate formatted link logs from privacy-safe source cleanup and upload plan audits, persist audit fallback paths, redact support bundles, and expose audit diagnostics safely. Improve queue selection and destructive-action clarity, show the Settings save action only while changes are pending, and add regression coverage for all updated behavior.
59 lines
2.6 KiB
JavaScript
59 lines
2.6 KiB
JavaScript
const nodePath = require('path');
|
|
|
|
function getUploadAuditLogPath(uploadLogPath, pathApi = nodePath) {
|
|
if (typeof uploadLogPath !== 'string' || !uploadLogPath.trim()) return null;
|
|
return pathApi.join(pathApi.dirname(uploadLogPath), 'upload-audit.log');
|
|
}
|
|
|
|
function createUploadAuditWriter(options) {
|
|
const source = options && typeof options === 'object' ? options : {};
|
|
const fs = source.fs;
|
|
const path = source.path || nodePath;
|
|
const resolveUploadLogTarget = source.resolveUploadLogTarget;
|
|
const rotateLogFile = typeof source.rotateLogFile === 'function' ? source.rotateLogFile : () => {};
|
|
const invalidateUploadLogTarget = typeof source.invalidateUploadLogTarget === 'function' ? source.invalidateUploadLogTarget : () => {};
|
|
const persistFallbackLogPath = typeof source.persistFallbackLogPath === 'function' ? source.persistFallbackLogPath : async () => {};
|
|
const reportError = typeof source.reportError === 'function' ? source.reportError : () => {};
|
|
const retryDelays = Array.isArray(source.retryDelays) && source.retryDelays.length > 0 ? source.retryDelays : [0, 100, 250];
|
|
const maxBytes = Number.isFinite(source.maxBytes) ? source.maxBytes : 10 * 1024 * 1024;
|
|
const maxBackups = Number.isFinite(source.maxBackups) ? source.maxBackups : 2;
|
|
let activePath = null;
|
|
|
|
if (!fs || !fs.promises || typeof fs.promises.appendFile !== 'function' || typeof resolveUploadLogTarget !== 'function') {
|
|
throw new TypeError('createUploadAuditWriter requires fs and resolveUploadLogTarget');
|
|
}
|
|
|
|
async function append(line, label) {
|
|
let excludedPath = null;
|
|
for (const delay of retryDelays) {
|
|
if (delay) await new Promise(resolve => setTimeout(resolve, delay));
|
|
const uploadTarget = resolveUploadLogTarget(excludedPath);
|
|
const targetPath = uploadTarget && getUploadAuditLogPath(uploadTarget.path, path);
|
|
if (!targetPath) continue;
|
|
try {
|
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
rotateLogFile(targetPath, maxBytes, maxBackups);
|
|
await fs.promises.appendFile(targetPath, line, 'utf-8');
|
|
activePath = targetPath;
|
|
if (uploadTarget.isFallback) {
|
|
try {
|
|
await persistFallbackLogPath(uploadTarget.path);
|
|
} catch (error) {
|
|
reportError('audit-fallback-persist', error);
|
|
}
|
|
}
|
|
return true;
|
|
} catch (error) {
|
|
excludedPath = uploadTarget.path;
|
|
invalidateUploadLogTarget();
|
|
reportError(label, error);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return { append, getActivePath: () => activePath };
|
|
}
|
|
|
|
module.exports = { getUploadAuditLogPath, createUploadAuditWriter };
|