Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | 1x 1x 2x 2x 1x 1x 1x 1x 107x 7x 7x 1x 1x 11x 11x 12x 11x 11x 11x 75x 2x 9x 25x 9x 3x 1x 2x 2x 2x 2x 3x 3x 1x 2x 2x | import { numericSettingsFields, sanitizeNumericSettings, validateNumericSettingValue, isPlainObject } from '@shared/settingsValidation'
import type { AppSettings } from '@shared/types'
import type { AutoLaunchPort } from '@main/ports/desktop'
import type { SettingsRepository } from '@main/ports/repositories'
const booleanFields = [
'autoStartBreaks',
'autoStartFocus',
'notificationsEnabled',
'soundEnabled',
'openAtLogin',
'startToTray',
'closeToTray'
] as const
const validateBooleanField = (value: unknown, field: (typeof booleanFields)[number]): boolean => {
Eif (typeof value !== 'boolean') {
throw new Error(`${field} must be a boolean`)
}
return value
}
const validateThemePreference = (value: unknown): AppSettings['themePreference'] => {
Iif (value === 'system' || value === 'light' || value === 'dark') {
return value
}
throw new Error('themePreference must be one of system, light, dark')
}
const knownSettingsFields = [...booleanFields, ...numericSettingsFields, 'themePreference'] as const
const hasOwn = (value: Record<string, unknown>, key: string): boolean => Object.prototype.hasOwnProperty.call(value, key)
export class SettingsService {
constructor(
private readonly settings: SettingsRepository,
private readonly autoLaunch: AutoLaunchPort
) {}
async get(): Promise<AppSettings> {
const current = await this.settings.get()
return this.sanitizeStoredSettings(current)
}
async update(patch: Partial<AppSettings>): Promise<AppSettings> {
Iif (!isPlainObject(patch)) {
throw new Error('settings patch must be a plain object')
}
for (const field of Object.keys(patch)) {
Iif (!(knownSettingsFields as readonly string[]).includes(field)) {
throw new Error(`Unknown settings field: ${field}`)
}
}
const nextPatch: Partial<AppSettings> = {}
const patchRecord = patch as Record<string, unknown>
for (const field of booleanFields) {
if (hasOwn(patchRecord, field)) {
nextPatch[field] = validateBooleanField(patchRecord[field], field)
}
}
for (const field of numericSettingsFields) {
if (hasOwn(patchRecord, field)) {
nextPatch[field] = validateNumericSettingValue(patchRecord[field], field)
}
}
if (hasOwn(patchRecord, 'themePreference')) {
nextPatch.themePreference = validateThemePreference(patchRecord.themePreference)
}
const updated = await this.settings.update(nextPatch)
const sanitizedUpdated = await this.sanitizeStoredSettings(updated)
Iif (hasOwn(patchRecord, 'openAtLogin') || hasOwn(patchRecord, 'startToTray')) {
this.autoLaunch.setOpenAtLogin(sanitizedUpdated.openAtLogin, sanitizedUpdated.startToTray)
}
return sanitizedUpdated
}
private async sanitizeStoredSettings(settings: AppSettings): Promise<AppSettings> {
const { normalized, patch } = sanitizeNumericSettings(settings as Record<(typeof numericSettingsFields)[number], unknown>)
if (Object.keys(patch).length === 0) {
return settings
}
await this.settings.update(patch)
return {
...settings,
...normalized
}
}
}
|