Commit Graph

402 Commits

Author SHA1 Message Date
Administrator
d720ba295a feat(log): add per-session log mode (one file per app launch)
Adds a third choice next to the existing single-file and per-day modes: a new
log file is created at every app start (process boot) and used until the app is
closed. A close → reopen of the app starts a new session, hence a new file.
File pattern: fileuploader-session-YYYY-MM-DD_HH-MM-SS-<pid>.log.

The boolean sessionLog field — misnamed: it actually toggled daily mode — is
replaced by a logMode enum: "single" | "daily" | "session". The misnomer made
the migration the trap to watch: existing users with sessionLog:true must land
on "daily", NOT "session". normalizeLogMode handles this and is unit-tested.

- lib/log-mode.js (new, pure, dual CJS/window export): normalizeLogMode +
  resolveLogFileName + format helpers. No fs, no Date.now() at call time.
- config-store.js: normalize at the single load() boundary so downstream
  readers consume logMode only. logMode is deliberately NOT seeded in DEFAULTS
  (would beat the legacy migration after merge).
- main.js: stamp SESSION_ID once at process start (with pid hedge against
  same-second restart collisions); getLogFilePath and buildFallbackLogName
  switch on mode via the lib. _resolveUploadLogTarget cache key is now just
  the primary path, which already encodes mode/date/session — self-invalidates.
- renderer: <select> with three German labels replaces the old checkbox;
  saveSettings writes logMode; index.html loads the lib so window.LogMode is
  available in renderSettings.
- Tests: 14 log-mode tests (incl. legacy-migration regression), 3 config-store
  tests (defaults, legacy migration, round-trip all three values). 200/200.

End-to-end simulated locally: two launches → two distinct session files; PID
hedge produces distinct names even within the same second.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 14:41:06 +02:00
Administrator
1c8514e127 docs(lessons): doodstream live-diagnosis findings (API path verified viable)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 22:41:49 +02:00
Administrator
1f622c5cc2 release: v3.3.34 2026-05-28 22:39:10 +02:00
Administrator
61853e7d4d fix(doodstream): force newest-first in file-list recovery (verified on 90k-file account)
Live diagnosis against the real account: it holds 90,548 files. The recovery
fetched only page 1 of /api/file/list without forcing order, so a just-uploaded
file could be missed if the default sort isn't newest-first. Add
sort=created&order=desc — verified to return the account's newest uploads first —
so the codeless-result recovery reliably finds the file regardless of account
size.

Diagnosis also confirmed the fix's premise: the API key is valid, /api/upload/server
returns a working node even on a lapsed-premium account, and uploads DO land
server-side (many Burn Notice episodes uploaded in the failure window are present).
So the API path returns the filecode directly and sidesteps the intermittent
web-form empty-result; entering the API key makes uploads succeed. 183/183.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 22:38:43 +02:00
Administrator
287ebde1f5 release: v3.3.33 2026-05-28 22:29:03 +02:00
Administrator
9ae5d312e1 fix(doodstream): web upload submits the live form's fields (not stale hardcoded)
Direct improvement to the login/web path (no API key needed): we were POSTing a
stale field set — sess_id + utype=reg + file — but doodstream's CURRENT upload
form dropped `utype` and added file_title / fakefilepc / submit_btn. Submitting
an incomplete/stale field set can make the CDN node accept the bytes but skip
the registration step (→ the empty result form with no fn). Now we parse the
live upload form (already fetched in _getUploadServer) and replicate ALL its
non-file fields faithfully — exactly what the browser submits — while keeping
sess_id (the fresh node token) and utype as a harmless compatibility extra.

- _parseUploadFormFields(html): pull every named input/button from the upload
  form, excluding the file input (streamed separately). Adapts to whatever
  fields doodstream uses now rather than hardcoding.
- upload() builds the multipart from those fields; minimal known-good fallback
  if the form wasn't parsed.
- Tests: real-form extraction (incl. file-input exclusion) + no-form safety. 183/183.

Low regression risk (superset of the previously-working fields). Whether it
resolves the large-file empty-form is for the server run; the API path
(3.3.31/32) remains the reliable route when a key is available/derivable.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 22:28:24 +02:00
Administrator
d24fd54e83 test(doodstream): end-to-end integration test for the API upload + recovery path
Closes the gap between the unit-tested parseDoodstreamResult and the real
uploadFile orchestration. Mocks the undici transport (reassign undici.request +
refresh the hosters cache; mock.module needs an experimental flag npm test
doesn't pass) and global fetch, then drives the full doodstream API path against
the doc-verified response shapes:
- filecode returned directly in result[0].filecode → used.
- codeless 2xx → recovered by polling file/list and name-matching the title.
- codeless + file never appears → throws with err.hosterTransient=true (so the
  account is not blacklisted).

Verified live this session: doodapi.co returns {"status":400,"msg":"Invalid
key"} for a bad key, so validation/list logic keys off status correctly.

Also makes the recovery poll count/delay tunable via __test.DOODSTREAM_POLL
(same 12 × 2.5 s defaults — non-behavioral) so the exhaustion test runs in ms.
Full suite 181/181.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 22:20:21 +02:00
Administrator
d2f903b8ba release: v3.3.32 2026-05-28 22:12:17 +02:00
Administrator
fc48f20db5 fix(doodstream): recover codeless API uploads by polling the file list by name
Backstop for the API path: if the doodapi upload POST returns no filecode (the
same backend registration hiccup that empties the web form), poll
doodapi.co/api/file/list for a newly-appeared file whose normalized title
matches what we uploaded, and claim its code — instead of failing the upload.

This is the exact recovery byse already uses in this file for the identical
symptom (large MKV, server-side "OK" but empty immediate response, file shows up
in the account shortly after). Doodstream is the same XFileSharing family with
the same doodapi-style API, and it directly addresses the user's observation
that the same file often succeeds on a second run.

- _fetchDoodstreamFileList / _resolveDoodstreamUploadByName: list via
  /api/file/list?key=&per_page=200, baseline-diff + exact normalized-title match
  (never "take the only new one", so parallel uploads can't claim each other's
  files), 12 polls × 2.5s.
- uploadFile snapshots a doodstream baseline before upload and polls after a
  codeless result, before the hosterTransient throw.

Verified solo: doodapi.co is reachable and returns {"status":400,"msg":"Invalid
key"} for a bad key, so the validation/list path keys off status correctly.
178/178. The real large-file run on the server is the final confirmation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 22:11:46 +02:00
Administrator
6286bca7c6 release: v3.3.31 2026-05-28 22:05:52 +02:00
Administrator
84c48ad7d6 fix(doodstream): login path auto-derives the API key → uploads via reliable API
The user uploads with username/password (login), so 3.3.30's "use API when a
key is configured" did nothing for them — and the web-form upload keeps failing
with empty forms on large files. Fix the LOGIN path itself: after logging in,
pull the account's API key out of the logged-in session and upload via the
official doodapi API (which returns result[0].filecode directly, no empty form).
The user keeps using login and configures nothing.

How the key is derived without knowing doodstream's (cookie-gated, unseen)
settings DOM: brute-force candidate extraction + API validation.
- DoodstreamUploader.deriveApiKey(): fetch the logged-in settings page
  (?op=my_account / /settings), pull every plausible long token from form-field
  values + element contents (ranked: tokens near an "api" mention first), and
  validate each against doodapi.co/api/account/info — only the account's real
  key returns status 200. A wrong guess is therefore harmless (fails validation
  → web fallback). Logs the raw settings HTML when nothing validates, so the
  scrape can be refined from a real capture if doodstream's markup differs.
- upload-manager: doodstream login-path now resolves the key ONCE per batch
  (cached by accountId; '' = tried-none) and routes to the API when found, else
  the existing web-form upload. Keyless accounts: one extra probe-login per
  batch, then unchanged.
- Tests: candidate extraction (value/textarea/api_key shapes, api-context
  ranking), validate-then-pick, null→web-fallback, preset short-circuit. 178/178.

If derivation works the login path now uploads via the API. It does NOT change
doodstream's backend; the server run confirms. Falls back safely if no key.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 22:05:20 +02:00
Administrator
329f768e2b docs(lessons): doodstream API-vs-web-scraping fix + empty-form root cause
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 21:50:18 +02:00
Administrator
35314ee3ed release: v3.3.30 2026-05-28 21:48:49 +02:00
Administrator
76c56cf13b fix(doodstream): extend 3.3.29 account-poison protection to the API path
The new API upload path POSTs to the same cloudatacdn.com nodes as the web
path, so it can hit the same backend flake — a 2xx response with no filecode, or
a transient "no servers available" from /api/upload/server (now that the stale
fallback node is gone). hosters.uploadFile threw GENERIC errors for both, which
the upload-manager would treat as an account failure → mark-failed →
pre-job-swap-blocked on the next batch: the exact symptom 3.3.29 fixed for the
web path, reintroduced via the unprotected API path.

Tag both API-path analogs of the empty form as err.hosterTransient=true:
- codeless 2xx ("lieferte keine file_code-Antwort") — bytes accepted, no link.
- transient "no upload server" (shouldRetryServerLookup true: no-servers/busy/
  try-again) — but NOT genuine auth failures (invalid key/unauthorized), which
  stay classified as account errors.

The upload-manager checks _isHosterTransientError (flag-based) before the
account-error classifier, so both now fail the file without blacklisting the
account. Consumption side already covered by the 3.3.29 regression test. 173/173.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 21:48:13 +02:00
Administrator
a8d81cbf0d fix(doodstream): upload via the doodapi API when an API key exists
Root cause of the recurring "kein Filecode — Server gab leeren Link zurueck":
the web-session upload flow gets the filecode back inside an XFileSharing HTML
form, and on long/large uploads that form comes back empty (no fn). Verified
research: doodstream's server-side file-registration callback times out under
large-file load, so the upload "succeeds" (bytes sent, HTTP 200) but no filecode
is minted — and because registration failed, the file is NOT in the file list
either, so polling can't recover it. The web path also rides a per-page-load
sess_id token that ages over the multi-minute upload.

The official doodapi.co JSON API has no such failure mode for result retrieval:
the upload response returns result[0].filecode directly, and it authenticates
with a persistent api_key (no aging sess_id). Git history confirms the API was
doodstream's ORIGINAL upload path (initial commit); web login was added later
only "as an alternative to API key" — so preferring the key restores the
intended primary path rather than fighting a deliberate choice.

- lib/account-auth.js (new, pure, unit-tested): selectUploadAuth() prefers the
  doodstream API key over username/password; all other hosters unchanged.
- main.js buildTaskFromAccount delegates to it → a doodstream account with an
  apiKey now routes through hosters.uploadFile (doodapi API) instead of the web
  uploader; keyless accounts keep using web login.
- hosters.js: drop the stale hardcoded fallback node from the doodstream API
  config (same dead tr1128ve host removed from the web path) so a failed server
  lookup throws cleanly instead of uploading into a dead end.
- Tests: 8 routing cases (doodstream key-preference, keyless fallback, voe
  unaffected, authType=api, null-safety). Full suite 173/173.

This eliminates the empty-form failure mode for result retrieval when a key is
configured. It does NOT change doodstream's backend — whether the large-file
timeout recurs (now as a structured JSON error, not a silent empty form) is for
the server run to confirm. Requires a doodstream API key on the account.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 21:42:19 +02:00
Administrator
13de55253b release: v3.3.29 2026-05-27 20:35:29 +02:00
Administrator
166b04c526 fix(upload): classify doodstream empty-form as hoster-transient (don't kill account)
The "kein Filecode — Server gab leeren Link zurueck" error was treated as a
generic upload failure → after retries exhausted, the manager called mark-failed
and added the account to _failedAccounts → next batch re-primed with
primedFailed=1 → pre-job-swap-blocked because no fallback override exists for a
single-account hoster. One server-side flake permanently poisoned the session.

It's not an account problem — same account + same file works on a later try.
This is a doodstream-backend processing flake (empty CDN form, no fn / no st),
the same class as a transient network error: don't blacklist, just fail this
file cleanly.

- doodstream-upload.js: tag the empty-form throw with err.hosterTransient=true
  (explicit flag, primary signal — matches the err.accountError / err.fileRejected
  pattern already used elsewhere).
- upload-manager.js: new _isHosterTransientError classifier (flag first, message
  regex as defensive fallback). In the retry loop: break on first hit (server
  flake won't clear in 3 s, re-uploading the file 4× is pure bandwidth waste).
  Post-loop: dedicated branch that emits the final error WITHOUT blacklisting
  the account — same shape as the existing transient-network branch.
- Tests: classifier unit tests (flag path, regex path, negatives) + regression
  test that proves the account is NOT added to _failedAccounts and mark-failed
  does NOT fire. Drops the hoster-transient test from ~19 s to ~1.5 ms,
  confirming the in-loop fast-break works.

We now fail fast on this error class instead of retrying — the next-batch
manual retry is the recovery path, and the account stays usable for it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 20:34:56 +02:00
Administrator
f0f1564322 release: v3.3.28 2026-05-25 01:09:30 +02:00
Administrator
af51bebaf7 fix(queue): stop auto-dedup from deleting pending jobs on restart/update
Reproduced from a real saved config: pendingQueue held 4 'preview' jobs (one
file across 4 hosters); the queue saved + restored correctly. But
_autoDeduplicateFromLog (runs at init after restore) removed jobs whose
fileName|hoster appeared ANYWHERE in the lifetime fileuploader.log, regardless
of status — so all 4 pending previews were deleted and the queue showed the
empty "Dateien hierhin ziehen" state. Looked update-specific only because the
server restarts on update; a plain restart did the same.

- New lib/queue-dedup.js (pure, dual CJS/window export like queue-prune.js):
  partitionRestoredJobsByLog drops ONLY 'done' jobs that match the log. Pending
  (preview/queued) and failed (error/aborted) jobs always survive — they're
  intentional queued work (often a deliberate re-upload of a previously
  uploaded file). Manual importUploadLog stays separate/explicit.
- renderer wires it in; index.html loads the module before app.js.
- Tests: 5 cases incl. the exact reproduced scenario (4 previews all in log ->
  0 removed). Full suite 162/162.

Verified against the user's real electron-config.json + fileuploader.log: old
logic removed 4/4 (empty queue), new logic removes 0/4 (queue preserved).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 01:08:59 +02:00
Administrator
3ef3e074e6 release: v3.3.27 2026-05-25 00:44:51 +02:00
Administrator
f237d0f97a fix(doodstream): survive transient network blips around the upload
After 3.3.26 fixed the filecode parsing, the remaining intermittent failure is
a generic "fetch failed" — a transient network error on one of the requests
around the multi-minute upload. Can't tell from one log line whether it's the
server-discovery GET or the post-upload result-submit, so harden both:

- _fetch (the native-fetch chokepoint for discovery, redirects, result-submit):
  retry up to 3x with short backoff on a thrown network error, each attempt
  bounded by a 20s timeout (Node fetch has none by default). Caller aborts are
  not retried. The big file upload (undici) is retried at the upload-manager
  level, not here.
- result-submit is now best-effort: if it still fails after retries but we
  already hold the filecode from the CDN response, return that instead of
  discarding a completed upload.
- label the undici upload-POST error with phase + MB sent + node, preserving the
  original message so transient classification still matches.
- eslint: add AbortSignal to globals.
- Tests: _fetch transient-retry path (10 doodstream tests total).

"fetch failed" is already classified transient by upload-manager, so this is
additive resilience; next logs will show if anything still slips through.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 00:44:20 +02:00
Administrator
8f500c590e release: v3.3.26 2026-05-25 00:32:54 +02:00
Administrator
18a875a764 fix(doodstream): use current page format (form action + matching sess_id)
The 3.3.25 diagnostics captured the live upload page: doodstream moved the
upload server from a `srv_url` JS variable into the multipart form's action,
e.g. action="https://xxx.cloudatacdn.com/upload/01?SESSID", with a per-page
session token in the query that matches the page's hidden sess_id input. The
old parser found neither and fell through to the stale hardcoded node, which
returns an empty filecode.

- Parse the upload server from the form action (matched via the /upload/ path),
  un-escaping &amp; in the query string.
- Refresh this.sessId from the SAME page (only on action match) so the
  multipart sess_id field matches the node URL's token; login-time and node
  tokens otherwise diverge. Keep the existing sessId if the input is absent.
- Keep the legacy ?op=upload_server JSON and srv_url paths as fallbacks; the
  fail-fast throw from 3.3.25 stays as the last resort.
- Tests: form-action parse, sess_id refresh, &amp; un-escape (9 total).

Whether this fully resolves the uploads is for the next server logs to confirm;
both the node and sess_id fixes are individually correct.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 00:32:25 +02:00
Administrator
1e6bb27404 release: v3.3.25 2026-05-25 00:26:50 +02:00
Administrator
3a23d76f24 docs(lessons): packaged-Electron log paths + surface hoster status fields
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 00:26:16 +02:00
Administrator
52751df735 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>
2026-05-25 00:25:55 +02:00
Administrator
9794efde46 release: v3.3.24 2026-05-24 19:01:25 +02:00
Administrator
ce5f20b1e1 fix(doodstream): surface real upload-failure reason + fix dead prod debug log
The "upload_result Seite hat keinen filecode" error fired with no actionable
detail when Doodstream's CDN returned an empty filecode (fn). Root cause is
server-side: the page structure is unchanged, the link is just missing —
Doodstream's backend refused the file (copyright/hash match, duplicate, size,
quota). XFileSharing reports the reason in the `st` field, which we ignored.

- Surface `st`: non-OK status now throws "Doodstream lehnt Datei ab (Status: …)".
- Enrich the generic error with st, fn-state, and the CDN node for diagnosis.
- Fix debug-log path: wrote to __dirname/.. which is read-only (app.asar) in
  packaged builds, so production captured zero traces. Now uses Electron's
  writable userData dir, with repo-root fallback for tests/plain node.
- Add tests/doodstream-upload.test.js (4 tests) pinning the parse/error paths.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 19:00:52 +02:00
Administrator
996fc5aa17 release: v3.3.23 2026-05-23 15:46:57 +02:00
Administrator
bd42c86796 ux(log): clarify logToFile also affects restart dedup
Deep bug-hunt of the per-hoster logToFile feature found the feature
itself clean (7 data flows traced: secret-store leaves hosterSettings
alone, save round-trip preserves the key for account-less hosters,
backup import/export round-trips, updateSettings full-replaces with
default-true fallback, checkbox branch precedes numeric coercion,
boolean survives IPC→JSON→parse intact).

The one real interaction effect: _autoDeduplicateFromLog reads
fileuploader.log on startup to drop already-uploaded files from the
restored queue. With logToFile off for a hoster, its entries are
absent, so the same file could be re-uploaded after a restart. The
dedup↔log coupling predates this feature; the toggle just makes it
observable.

Make it transparent in the checkbox hint rather than silently
shipping the surprise. Full decoupling (a separate always-written
dedup index independent of the user-facing log) is a larger,
separate change with its own risk surface — deferred unless wanted.

147/147 tests still green.
2026-05-23 15:46:27 +02:00
Administrator
042f3d0ef9 release: v3.3.22 2026-05-23 15:33:59 +02:00
Administrator
ceab155a6c fix(css): render per-hoster logToFile checkbox as a checkbox, not a stretched box
The new "Links in Log schreiben" control reuses class .hs-input for
the autosave bind to pick it up — but .hs-input also carries the
text-input styling (flex:1, padding, background, border, max-width:
300px). Applied to a checkbox that produced a stretched, padded,
filled box instead of a normal tick box.

Add an .hs-input[type="checkbox"] override that resets flex/size/
padding/background/border so it renders as a plain 16×16 checkbox
beside its label, consistent with the other settings checkboxes.

Caught during the post-feature side-effect sweep (advisor flagged the
grid layout as the one thing self-checks couldn't cover). 147/147
tests still green.
2026-05-23 15:33:31 +02:00
Administrator
fb5c1caf43 release: v3.3.21 2026-05-23 15:31:48 +02:00
Administrator
57f8f0876e feat(log): per-hoster toggle for writing links to fileuploader.log
New per-hoster setting "Links in Log schreiben" (logToFile, default
on). When unchecked for a hoster, that hoster's successful upload
links are no longer written to fileuploader.log — other hosters keep
logging independently.

- lib/config-store.js: logToFile: true added to HOSTER_SETTINGS_DEFAULTS;
  merge-on-load gives every hoster the key (old configs included).
- renderer/app.js: checkbox per hoster panel + collection loop now
  handles type=checkbox (boolean) alongside the numeric fields. The
  autosave bind already special-cased checkboxes (change event).
- lib/log-policy.js (new): hosterLogToFileEnabled() — pure, opt-out
  semantics. Only an explicit logToFile===false disables; missing/
  malformed/non-true values all default ON so links are never
  silently dropped.
- main.js: shouldLogHosterToFile() reads the LIVE uploadManager
  .hosterSettings (so a mid-batch toggle takes effect at once), falls
  back to persisted config, then to enabled. Guards appendUploadLog
  in the done handler; skipped writes get a debugLog line.

Tests: 8 log-policy (defaults, opt-out, per-hoster independence,
malformed input) + 2 config-store (default true, persisted false
survives reload). 147/147 green, eslint clean.
2026-05-23 15:29:25 +02:00
Administrator
4c88c0a756 release: v3.3.20 2026-05-23 01:10:40 +02:00
Administrator
2208632154 ux(log): default fileuploader.log path is now the user's Desktop
In packaged builds path.dirname(process.execPath) resolves to
%LOCALAPPDATA%\Programs\Multi-Hoster-Upload — a hidden install
directory the user never visits and that NSIS may prune on
uninstall. Existing files written there were effectively invisible.

Change the unconfigured-default to app.getPath('desktop') instead.
If Desktop isn't available (rare), fall back to userData (Roaming),
and finally to the exe dir as a last resort. Dev mode (isPackaged
false) is unchanged — keeps the project dir for inspection.

Custom log paths set via the Settings UI override this and continue
to work as before. Existing users with old logs in the install dir
will just see a new fileuploader.log on the Desktop going forward;
the old file stays where it is (not auto-migrated).

137/137 tests still green.
2026-05-23 01:10:10 +02:00
Administrator
c741503665 release: v3.3.19 2026-05-23 01:03:43 +02:00
Administrator
950a322022 ux(accounts): hoster-specific login field labels — VOE shows "E-Mail" only
Generic "Username / E-Mail" label on every login-type account form
sent users down a confusing path on VOE: VOE only accepts an email
address (the web form is type=email, name=email), but the app's
label suggested either was fine. Logging in with a username
silently failed → upload-page fetch returned a login redirect → the
"VOE Upload: CSRF-Token nicht gefunden. Bist du eingeloggt?" error,
which doesn't point at the actual cause.

Add a tiny per-hoster override table. Currently only voe.sx is in
it: label "E-Mail", placeholder "E-Mail-Adresse", input type="email"
(so the browser's email-format hint kicks in too). All three
getCredsFieldsHtml call sites pass the hoster name — edit-mode,
add-mode initial render, and the hoster-select change handler.

Other hosters keep the existing "Username / E-Mail" wording.
137/137 tests still green.
2026-05-23 01:03:07 +02:00
Administrator
c995d090a5 release: v3.3.18 2026-04-28 12:00:05 +02:00
Administrator
166a49dd0c test(coalesce): extract done-removal coalescer + 11 unit tests
The microtask-coalesce path from 3.3.1 (queueMicrotask + Set so 500
finishing jobs become one queueJobs.filter pass instead of 500) lived
inline in renderer/app.js. Pulled out into lib/coalesced-set.js with
an injectable scheduler so a Node test can drive timing without
async waits.

API: makeCoalescedSet({ apply, scheduler? }) returns
  add(id)        — queue an id for the next batch
  drainSync()    — flush synchronously (used by beforeunload)
  pendingSize()  — diagnostics
  isScheduled()  — diagnostics

Renderer rewires the previous _pendingDoneRemovalIds + manual
queueMicrotask plumbing to the new helper. Optional-chained: if the
script fails to load, a slower per-event filter runs as fallback.

Coverage:
- multiple adds same tick → 1 apply, all ids deduped
- duplicate ids deduped
- batches between flushes stay independent
- add after flush re-schedules
- drainSync flushes synchronously, queued microtask becomes a no-op
- empty drainSync is a no-op
- throwing apply doesn't lock out subsequent batches
- default scheduler (queueMicrotask) runs eventually
- 5000-id burst still coalesces to 1 apply

137/137 green.
2026-04-28 11:59:32 +02:00
Administrator
f5256c437f release: v3.3.17 2026-04-28 11:54:25 +02:00
Administrator
d650a7395a chore(deps): npm audit fix --force — closes 12 deferred vulnerabilities
User explicitly authorized the major-version bump after the loop
flagged it as deferred. Two breaking-change upgrades land:

- electron-builder: 25.1.8 → 26.8.1
- electron:         33.4.11 → 41.3.0

Plus the transitive cleanup that the audit chain (@tootallnate/once,
http-proxy-agent, make-fetch-happen, node-gyp, @electron/rebuild,
app-builder-lib, dmg-builder, electron-builder-squirrel-windows, tar,
cacache, brace-expansion, @xmldom/xmldom) required.

Vulnerability count: 12 → 0.
35 packages added, 138 removed, 39 changed.

Verified: 126/126 unit tests still green. NSIS+portable build runs
end-to-end on the new toolchain (artifacts ~100 MB each due to the
electron 41 baseline). Renderer is Chromium-based as before; no
behaviour change expected on the user side, just a more current
runtime + signed-build pipeline.
2026-04-28 11:53:53 +02:00
Administrator
bd41aff769 docs: loop status update post-3.3.16 2026-04-28 11:51:05 +02:00
Administrator
2ea26f4b64 release: v3.3.16 2026-04-28 11:11:45 +02:00
Administrator
b1fe0cfefb fix(log): auto-rotate the other 3 internal log files (debug, rot, doodstream)
3.3.2 fixed fileuploader.log unbounded growth, but three siblings kept
growing without limit:

- upload-debug.log     (verbose, every IPC + progress event log line)
- account-rotation.log (every rotation decision)
- doodstream-debug.log (per-hoster trace from lib/doodstream-upload.js)

A multi-month dev install or a heavy production user could fill the
log dir with multi-GB files and slow every appendFile.

Wire all three through the same lib/log-rotation.js helper:
- upload-debug.log     → 25 MB cap, 2 numbered backups (~75 MB worst)
- account-rotation.log → 10 MB cap, 2 numbered backups (~30 MB worst)
- doodstream-debug.log → 10 MB cap, 1 numbered backup  (~20 MB worst)

The rotation check runs once per flush call (each is debounced or
already a once-per-event path), so the statSync overhead is
microscopic. _flushDebugLog passes a noop logger to avoid recursing
into itself; _flushRotLog and _debugLog (doodstream) use the normal
debugLog so any rotation surprises end up in upload-debug.log.

126/126 tests still green.
2026-04-28 11:11:24 +02:00
Administrator
6e68748ca0 release: v3.3.15 2026-04-28 10:39:29 +02:00
Administrator
10ae46c44d fix(upload): re-check cancellation after _sleep in rotation while-loop
The account-rotation while-loop entered with a signal.aborted /
stopAfterActive check (line 681) but then awaited _sleep(800) on
line 690 (waiting for main.js to resolve the next fallback) without
re-checking on the way out. If the user cancelled during that 800 ms
window the loop kept going — resolved the override, set up new
credentials, fired retrying-event, started a fresh attempt loop —
before _executeUpload's own signal handling finally noticed the
abort. Cancellation latency could therefore stretch by an extra
attempt's worth of work per still-spinning hoster.

One-line fix: add the same `if (signal.aborted || this.stopAfterActive)
break` after the await. Found by deep-audit MED-5.

126/126 tests still green; the fix is a guard on an already-tested
flow, no test infrastructure exists for cancel-during-rotation
specifically (would need fake-timer + mocked override-resolution).
2026-04-28 10:39:09 +02:00
Administrator
7267adfd03 release: v3.3.14 2026-04-28 10:12:52 +02:00
Administrator
0ba8bd3a2c fix(hosters): defensive null-payload guards in result parsers + 7 tests
When a hoster server replies with a body that JSON-parses to a
non-object (literal "null", a bare string, a number, a top-level
array), uploadFile's downstream code crashed:

  payload.msg          → TypeError on null
  payload.status       → TypeError on null
  config.parseResult() → TypeError inside parseDoodstreamResult
                         (payload.result) and parseByseResult
                         (payload.files / payload.result)

The user saw a confusing "Cannot read properties of null" instead of
a useful "server returned no JSON object". Found by deep-audit pass.

Fix in three places:

1. uploadFile (lib/hosters.js): after JSON.parse, normalise non-object
   payloads to {}. Subsequent `payload.X` accesses then return
   undefined and the existing fallback paths handle the empty case.

2. parseDoodstreamResult: defensive `payload && payload.result` so
   direct callers (tests, hypothetical future callers) get the same
   guarantee instead of relying on uploadFile to have normalised.

3. parseByseResult: same `payload || typeof payload !== 'object'`
   short-circuit at entry, plus null-checks on `f` (the first files
   entry) so a server returning [null] in files doesn't crash either.

Tests: 7 new unit tests covering null/undefined/string/number/array
payloads, malformed files entries, the fileRejected/accountError
classification (regression-pinning the 3.1.4 phrasing tweaks), and
the valid-filecode happy path. 126/126 green.
2026-04-28 10:12:32 +02:00
Administrator
74d7f8ce5a release: v3.3.13 2026-04-28 09:40:28 +02:00