1376c2dee8
Audit-pass over CODE-AUDIT.md (~48 items closed this pass; all verified — typecheck + 234 tests + eslint + prettier green). Correctness / bugs: - B3: match the release checksum to the asset's filename line (no wrong-hash verify) - B4: newline-safe metadata probe (one --print with a unit-separator delimiter) - B5 / L88: guard the meta event against canceled items; progress no longer promotes a queued item outside pump() - B7: cookie-login promise always resolves (handles destroy-without-close) - L146: trim parser rejects >2 colon-group times; M36: Library selection counts only actionable rows - L11 / L50 / L156 / L57 / L159 / L15 / L3: live queue count, empty-cookie message, schedule picker min, dead-code/comment cleanup Type safety: - Enable noUncheckedIndexedAccess + noFallthroughCasesInSwitch (15 real edge cases fixed) Resilience / Windows / metadata: - R5: settings write failure handled (no unhandled IPC rejection; reconciles to truth) - W1 / W5 / W6: min window size, seeded folder picker, parented sign-in window; L147 dead macOS branches removed - CL1: shared stdout markers; package/builder metadata (license, homepage, repository, copyright, tsbuildinfo glob) Copy / docs / tests: - M37 / SR9 dev-jargon cleanup in hints; M8 / M25 / M26 / L66 / L80 / L81 reconciled - New unit tests for L35 (isValidMediaItem) and L36 (compareVersions) This commit also checkpoints the previously-uncommitted feat/tray-background-clipboard work it builds on: background running + auto-download, library clipboard detection, tray, binary management & library scale, credential encryption at rest, the shared jsonStore and ui/ primitives, and the eslint/prettier tooling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
88 lines
2.7 KiB
TypeScript
88 lines
2.7 KiB
TypeScript
import { deflateSync } from 'zlib'
|
||
import { nativeImage, type NativeImage } from 'electron'
|
||
|
||
// CRC32 table polynomial used by PNG.
|
||
function crc32(buf: Buffer): number {
|
||
let crc = 0xffffffff
|
||
for (const b of buf) {
|
||
crc ^= b
|
||
for (let i = 0; i < 8; i++) crc = crc & 1 ? (crc >>> 1) ^ 0xedb88320 : crc >>> 1
|
||
}
|
||
return (crc ^ 0xffffffff) >>> 0
|
||
}
|
||
|
||
function pngChunk(type: string, data: Buffer): Buffer {
|
||
const t = Buffer.from(type, 'ascii')
|
||
const len = Buffer.alloc(4)
|
||
len.writeUInt32BE(data.length)
|
||
const crc = Buffer.alloc(4)
|
||
crc.writeUInt32BE(crc32(Buffer.concat([t, data])))
|
||
return Buffer.concat([len, t, data, crc])
|
||
}
|
||
|
||
/**
|
||
* Build a 16×16 RGBA PNG containing a solid-colour filled circle. Used for the
|
||
* Windows taskbar overlay badge. No external deps — pure Node.js (Buffer + zlib).
|
||
*/
|
||
function makeDotPng(r: number, g: number, b: number): NativeImage {
|
||
const W = 16,
|
||
H = 16
|
||
const px = Buffer.alloc(W * H * 4, 0) // transparent bg
|
||
const cx = W / 2,
|
||
cy = H / 2,
|
||
rad = W / 2 - 1.5
|
||
|
||
for (let y = 0; y < H; y++) {
|
||
for (let x = 0; x < W; x++) {
|
||
const dx = x + 0.5 - cx,
|
||
dy = y + 0.5 - cy
|
||
if (dx * dx + dy * dy <= rad * rad) {
|
||
const i = (y * W + x) * 4
|
||
px[i] = r
|
||
px[i + 1] = g
|
||
px[i + 2] = b
|
||
px[i + 3] = 255
|
||
}
|
||
}
|
||
}
|
||
|
||
// PNG: IHDR (8-bit RGBA, no interlace), filter-0 rows, deflated IDAT, IEND.
|
||
const ihdr = Buffer.alloc(13)
|
||
ihdr.writeUInt32BE(W, 0)
|
||
ihdr.writeUInt32BE(H, 4)
|
||
ihdr[8] = 8 // bit depth
|
||
ihdr[9] = 6 // colour type: RGBA
|
||
|
||
const rows = Buffer.alloc(H * (1 + W * 4))
|
||
for (let y = 0; y < H; y++) {
|
||
rows[y * (1 + W * 4)] = 0 // filter type: None
|
||
px.copy(rows, y * (1 + W * 4) + 1, y * W * 4, (y + 1) * W * 4)
|
||
}
|
||
|
||
const sig = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])
|
||
const png = Buffer.concat([
|
||
sig,
|
||
pngChunk('IHDR', ihdr),
|
||
pngChunk('IDAT', deflateSync(rows)),
|
||
pngChunk('IEND', Buffer.alloc(0))
|
||
])
|
||
return nativeImage.createFromBuffer(png)
|
||
}
|
||
|
||
// Lazy-initialised singletons — created on first use so this module is safe to
|
||
// import before the app is ready (nativeImage.createFromBuffer is fine post-ready).
|
||
let activeBadge: NativeImage | null = null
|
||
let errorBadge: NativeImage | null = null
|
||
|
||
/** Green dot badge for the taskbar overlay — shown when downloads are active. */
|
||
export function getActiveBadge(): NativeImage {
|
||
if (!activeBadge) activeBadge = makeDotPng(58, 185, 108) // #3ab96c
|
||
return activeBadge
|
||
}
|
||
|
||
/** Red dot badge for the taskbar overlay — shown when a download has errored. */
|
||
export function getErrorBadge(): NativeImage {
|
||
if (!errorBadge) errorBadge = makeDotPng(217, 48, 37) // #d93025
|
||
return errorBadge
|
||
}
|