src/main.js: 1262-line monolith with innerHTML injection sinks, unfrozen CONFIG, and duplicated logic #29
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
src/main.jsis 1262 lines / ~49 kB — the entire application in one side-effecting entry module. Verified onmainatfbfe1df.1.
innerHTMLinterpolation of values that are about to become user-configurableEight
innerHTMLassignment sites: lines 589, 705, 707, 715, 866, 896, 900, 1043. Two interpolate host data directly into raw HTML:src/main.js:559—${host.name}src/main.js:564—${host.url}src/main.js:871—${entry.message}into the debug logNo escaping helper exists anywhere in the file.
This is not exploitable today — every host is a hardcoded constant in
WAN_HOSTS. ButREADME.md:163plans "configurable host list (environment variable or config file)", which converts all three into live injection sinks the moment it lands. Fixing it now is cheap; fixing it after the config feature ships means auditing every call site under time pressure.2.
CONFIGis documented as frozen and is mutated at runtimesrc/main.js:10declares itconstwith noObject.freeze.src/main.js:1202doesCONFIG.updateInterval = newInterval;when the user changes the interval dropdown.README.md:61calls it a "Frozen configuration object". (The README half is tracked in #24; the code half is here — either freeze it and derive the mutable interval separately, or stop calling it frozen.)3. Undeclared instance property
AppState's constructor (src/main.js:264-272) declareswan,local,paused,tickCount._recoveryProbeIdis never declared there but is read at:999, written at:1009, and read/cleared at:1023-1025. The first read is againstundefined.4. Duplicated logic
HostState.medianLatency()(:246-256) and the inline median insideAppState.wanStats()(:292-297) are the same algorithm written twice.minLatency/maxLatency/averageLatency(:226-244) each independently re-filterthis.history— three passes where one would do."status-text text-xs ... col-span-2 mt-5"is written out verbatim five times: lines 724, 728, 732, 736, 1048.5. Magic numbers that the CONFIG object exists to hold
CONFIGwas created to centralize constants, yet these are hardcoded inline: history cap1000(:113); gateway probe timeout1500(:173); health thresholds10, 4, 4, 1000, 3(:332-337); latency colour thresholds50/100/200/500duplicated across two functions with parallel hardcoded hex and class values (:392-396,:402-406); sparkline margins (:412); canary count4(:1005); recovery interval500(:1019); resize delay100(:1255); sort cadence2/10(:973).The duplicated colour thresholds are the worst of these — changing a threshold requires editing two functions in lockstep or the figure colour and the sparkline colour silently disagree.
6. Nesting depth
CODE_STYLEGUIDE.md: "Avoid nestingif... invert the condition and usereturnto exit early." Three-plus levels at:472-484,:494-510,:514-524,:1033-1056(four sequentialifblocks with a nestedifat:1054),:1064-1079, and:169-193(four levels insidedetectGateway).7. Minor
:470-471,:500-501,:516-517, inconsistent with the one-per-statement style used everywhere else.SparklineRenderer._drawXAxis(ctx, w, h, m, cw)at:458never usesw.pushSamplewriteserror: result.error(:212) whilepushPausedwritespaused: true(:222) — two different shapes in one buffer, discriminated ad-hoc at:476.:548is a 300+ char SVG path,:593a 400+ char<h1>).Definition of done
textContentoverinnerHTMLstring assembly where the change is local; whereinnerHTMLstays, the interpolated values must be escaped.CONFIGand the code agree on mutability. EitherObject.freezeit and hold the user-adjustable interval elsewhere, or drop the "frozen" claim. State which you chose._recoveryProbeIdis declared in the constructor.CONFIG. The latency colour thresholds are defined once and both the figure-colour and sparkline-colour functions read from that single definition.wparameter is removed; the two history-entry shapes are reconciled or explicitly documented as a tagged union.make checkpasses.TODO.mdupdated in the same commit.(closes #N).Scope boundary — read this
Full modularization of the file is NOT in scope for this issue. Natural module boundaries already exist as comment banners and a future split would be roughly:
config.js(:3-141),log.js(:106-124),format.js(:143-161),net.js(:163-194,:349-386),state.js(:196-347),colors.js(:388-407),sparkline.js(:409-536),ui.js(:538-925),main.js(:927-1262). That is a large, high-churn change and it belongs in its own commit after the correctness fixes above have landed and after #21 gives us tests to refactor against.Do the safety and duplication fixes here. Extract a module only where item 5 or #21 genuinely requires it, and say so in the PR.
Deliberately not on the
1.0.0milestone: none of this blocks a tag, and the injection sinks are latent rather than live. It should land before the configurable-host-list feature does, whenever that is scheduled.Implementation requirements
CODE_STYLEGUIDE_JS.mdand the general styleguide's early-return guidance.maketargets andscript/entrypoints only.