Compare commits

...

2 Commits

Author SHA1 Message Date
xRangerDE
a7e189fef9 release: 4.6.40 live-status IPC payload trim
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 02:33:10 +02:00
xRangerDE
dd08f33dc6 perf: trim live-status batch IPC payload + skip empty broadcasts
The live-status batch poller (60s cadence, every streamer in the
watch list) was sending two things on every tick:
- `changes` — the diff vs. the previous tick, used by the renderer
- `snapshot` — the full Map<login, boolean> serialized as a record

Renderer destructures only `changes` (renderer-streamers.ts line 20).
The snapshot field was wire-noise. For a typical 30-50 streamer
watch list, that snapshot is ~1.5KB of JSON every minute, never
read on the other side. Dropped from the broadcast payload.

Initial-state sync still works: the renderer's
initLiveStatusSubscription calls window.api.getLiveStatusSnapshot()
once at boot to pre-fill its map. The broadcast is only for diffs.

Also added a short-circuit on the main side: if changes.length === 0
(every streamer's live status matched the cached value this tick),
don't broadcast at all. The renderer would just iterate an empty
array and trigger a no-op render; saves the wakeup entirely.

Type signature updates ride through preload.ts +
renderer-globals.d.ts so the API contract stays accurate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 02:33:09 +02:00
5 changed files with 12 additions and 9 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.6.39", "version": "4.6.40",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.6.39", "version": "4.6.40",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"axios": "^1.6.0", "axios": "^1.6.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.6.39", "version": "4.6.40",
"description": "Twitch VOD Manager - Download Twitch VODs easily", "description": "Twitch VOD Manager - Download Twitch VODs easily",
"main": "dist/main.js", "main": "dist/main.js",
"author": "xRangerDE", "author": "xRangerDE",

View File

@ -3770,10 +3770,13 @@ async function runLiveStatusBatchPoll(): Promise<void> {
} }
} }
if (mainWindow) { if (mainWindow && changes.length > 0) {
const snapshot: Record<string, boolean> = {}; // Renderer only consumes `changes` — initial state comes via
for (const [k, v] of liveStatusByLogin.entries()) snapshot[k] = v; // the get-live-status-snapshot IPC at boot. Don't ship the
mainWindow.webContents.send('live-status-batch-update', { changes, snapshot }); // full map on every tick (was ~1.5KB JSON per 60s with zero
// consumer-side use). Also skip the broadcast entirely when
// nothing actually changed.
mainWindow.webContents.send('live-status-batch-update', { changes });
} }
} catch (e) { } catch (e) {
appendDebugLog('live-status-poll-failed', String(e)); appendDebugLog('live-status-poll-failed', String(e));

View File

@ -94,7 +94,7 @@ contextBridge.exposeInMainWorld('api', {
getStreamerProfile: (login: string, forceRefresh?: boolean) => ipcRenderer.invoke('get-streamer-profile', login, forceRefresh), getStreamerProfile: (login: string, forceRefresh?: boolean) => ipcRenderer.invoke('get-streamer-profile', login, forceRefresh),
getVodStoryboard: (vodId: string) => ipcRenderer.invoke('get-vod-storyboard', vodId), getVodStoryboard: (vodId: string) => ipcRenderer.invoke('get-vod-storyboard', vodId),
getLiveStatusSnapshot: () => ipcRenderer.invoke('get-live-status-snapshot'), getLiveStatusSnapshot: () => ipcRenderer.invoke('get-live-status-snapshot'),
onLiveStatusBatchUpdate: (callback: (info: { changes: Array<{ login: string; isLive: boolean }>; snapshot: Record<string, boolean> }) => void) => { onLiveStatusBatchUpdate: (callback: (info: { changes: Array<{ login: string; isLive: boolean }> }) => void) => {
ipcRenderer.on('live-status-batch-update', (_, info) => callback(info)); ipcRenderer.on('live-status-batch-update', (_, info) => callback(info));
}, },
searchArchive: (filter: Record<string, unknown>) => ipcRenderer.invoke('search-archive', filter), searchArchive: (filter: Record<string, unknown>) => ipcRenderer.invoke('search-archive', filter),

View File

@ -347,7 +347,7 @@ interface ApiBridge {
getStreamerProfile(login: string, forceRefresh?: boolean): Promise<StreamerProfile | null>; getStreamerProfile(login: string, forceRefresh?: boolean): Promise<StreamerProfile | null>;
getVodStoryboard(vodId: string): Promise<VodStoryboard | null>; getVodStoryboard(vodId: string): Promise<VodStoryboard | null>;
getLiveStatusSnapshot(): Promise<Record<string, boolean>>; getLiveStatusSnapshot(): Promise<Record<string, boolean>>;
onLiveStatusBatchUpdate(callback: (info: { changes: Array<{ login: string; isLive: boolean }>; snapshot: Record<string, boolean> }) => void): void; onLiveStatusBatchUpdate(callback: (info: { changes: Array<{ login: string; isLive: boolean }> }) => void): void;
searchArchive(filter: { searchArchive(filter: {
query?: string; query?: string;
type?: 'all' | 'live' | 'vod' | 'chat' | 'events'; type?: 'all' | 'live' | 'vod' | 'chat' | 'events';