Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@ import { JsonPointerMovementSettingsStore } from './pointer-movement-settings-st
import { registerServerIpc } from './server-ipc';
import { registerSettingsWindowIpc } from './settings-window-ipc';
import { secondInstanceAction } from './single-instance';
import { appendStartupDiagnostics } from './startup-diagnostics';
import { registerSystemStartupIpc } from './system-startup-ipc';
import { shouldStartHidden, SystemStartupService } from './system-startup';
import { createSwitchifyTray, type SwitchifyTray } from './tray';
import { registerUpdateIpc } from './updates/update-ipc';
import { UpdateService } from './updates/update-service';
import { WINDOWS_APP_USER_MODEL_ID } from './windows-app-user-model-id';
import { createWindowsStartupRegistry } from './windows-startup-registry';

const isDev = Boolean(process.env.ELECTRON_RENDERER_URL);
let controlService: ControlService | null = null;
Expand Down Expand Up @@ -259,10 +261,31 @@ if (!gotSingleInstanceLock) {
platform: process.platform,
isPackaged: app.isPackaged,
executablePath: process.execPath,
appUserModelId: WINDOWS_APP_USER_MODEL_ID,
getLoginItemSettings: (options) => app.getLoginItemSettings(options),
setLoginItemSettings: (settings) => app.setLoginItemSettings(settings)
startupRegistry: createWindowsStartupRegistry()
});
void systemStartup
.getSettings()
.then((settings) => {
appendStartupDiagnostics(join(app.getPath('userData'), 'startup-diagnostics.jsonl'), {
startedAt: new Date().toISOString(),
version: app.getVersion(),
isPackaged: app.isPackaged,
platform: process.platform,
executablePath: process.execPath,
argv: process.argv,
startHidden,
startupRegistration: settings.registration
? {
startWithSystem: settings.startWithSystem,
registeredCommand: settings.registration.registeredCommand,
startupApproved: settings.registration.startupApproved
}
: undefined
});
})
.catch((error) => {
console.warn(error instanceof Error ? error.message : 'Could not write startup diagnostics.');
});
const pairingStore = new JsonPairingStore(join(app.getPath('userData'), 'pairing-state.json'));
const cursorOverlaySettingsStore = new JsonCursorOverlaySettingsStore(
join(app.getPath('userData'), 'cursor-overlay-settings.json')
Expand Down
81 changes: 81 additions & 0 deletions src/main/startup-diagnostics.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { describe, expect, it } from 'vitest';
import { appendStartupDiagnostics, type StartupDiagnosticsEntry } from './startup-diagnostics';

describe('appendStartupDiagnostics', () => {
it('appends a JSONL diagnostics entry', () => {
const filePath = diagnosticsPath();

appendStartupDiagnostics(filePath, entry({ startedAt: '2026-06-24T10:00:00.000Z' }));

const lines = readLines(filePath);
expect(lines).toHaveLength(1);
expect(JSON.parse(lines[0])).toMatchObject({
startedAt: '2026-06-24T10:00:00.000Z',
version: '0.1.15',
startHidden: true
});
});

it('keeps only the newest 50 lines', () => {
const filePath = diagnosticsPath();

for (let index = 0; index < 55; index += 1) {
appendStartupDiagnostics(filePath, entry({ startedAt: `2026-06-24T10:${String(index).padStart(2, '0')}:00.000Z` }));
}

const lines = readLines(filePath);
expect(lines).toHaveLength(50);
expect(JSON.parse(lines[0]).startedAt).toBe('2026-06-24T10:05:00.000Z');
expect(JSON.parse(lines[49]).startedAt).toBe('2026-06-24T10:54:00.000Z');
});

it('creates the parent directory if it is missing', () => {
const filePath = join(mkdtempSync(join(tmpdir(), 'switchify-startup-')), 'nested', 'startup.jsonl');

appendStartupDiagnostics(filePath, entry());

expect(readLines(filePath)).toHaveLength(1);
});

it('drops malformed existing lines without throwing', () => {
const filePath = diagnosticsPath();
writeFileSync(filePath, 'not json\n{"startedAt":"old"}\n', 'utf8');

appendStartupDiagnostics(filePath, entry());

const lines = readLines(filePath);
expect(lines).toHaveLength(2);
expect(JSON.parse(lines[0])).toEqual({ startedAt: 'old' });
});
});

function diagnosticsPath(): string {
return join(mkdtempSync(join(tmpdir(), 'switchify-startup-')), 'startup-diagnostics.jsonl');
}

function readLines(filePath: string): string[] {
return readFileSync(filePath, 'utf8')
.split(/\r?\n/)
.filter(Boolean);
}

function entry(overrides: Partial<StartupDiagnosticsEntry> = {}): StartupDiagnosticsEntry {
return {
startedAt: '2026-06-24T10:00:00.000Z',
version: '0.1.15',
isPackaged: true,
platform: 'win32',
executablePath: 'C:\\Program Files\\Switchify PC\\Switchify PC.exe',
argv: ['Switchify PC.exe', '--start-hidden'],
startHidden: true,
startupRegistration: {
startWithSystem: true,
registeredCommand: '"C:\\Program Files\\Switchify PC\\Switchify PC.exe" --start-hidden',
startupApproved: 'enabled'
},
...overrides
};
}
50 changes: 50 additions & 0 deletions src/main/startup-diagnostics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { dirname } from 'node:path';
import type { StartupApprovedState } from '../shared/system-startup';

export type StartupDiagnosticsEntry = {
startedAt: string;
version: string;
isPackaged: boolean;
platform: NodeJS.Platform;
executablePath: string;
argv: string[];
startHidden: boolean;
startupRegistration?: {
startWithSystem: boolean;
registeredCommand: string | null;
startupApproved: StartupApprovedState;
};
};

const MAX_STARTUP_DIAGNOSTICS_LINES = 50;

export function appendStartupDiagnostics(filePath: string, entry: StartupDiagnosticsEntry): void {
try {
mkdirSync(dirname(filePath), { recursive: true });
const existingLines = readExistingLines(filePath);
const nextLines = [...existingLines, JSON.stringify(entry)].slice(-MAX_STARTUP_DIAGNOSTICS_LINES);
writeFileSync(filePath, `${nextLines.join('\n')}\n`, 'utf8');
} catch (error) {
console.warn(error instanceof Error ? error.message : 'Could not write startup diagnostics.');
}
}

function readExistingLines(filePath: string): string[] {
try {
return readFileSync(filePath, 'utf8')
.split(/\r?\n/)
.map((line) => line.trim())
.filter(Boolean)
.filter((line) => {
try {
JSON.parse(line);
return true;
} catch {
return false;
}
});
} catch {
return [];
}
}
Loading