Skip to content

Commit 9d6cd5e

Browse files
committed
fix: validate json before apply
1 parent f40d245 commit 9d6cd5e

2 files changed

Lines changed: 66 additions & 10 deletions

File tree

src/main/ipc/handlers/theme.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ let themeWatcherTimer: NodeJS.Timeout | null = null
6060
let watchedThemesDir: string | null = null
6161
let watcherStartToken = 0
6262
let chokidarWatchLoader: Promise<ChokidarWatch> | null = null
63+
const reportedThemeIssues = new Map<string, string>()
6364

6465
type ChokidarWatch = (
6566
path: string | readonly string[],
@@ -100,6 +101,27 @@ function ensureThemesDir(): string {
100101
return THEMES_DIR
101102
}
102103

104+
function reportThemeIssue(
105+
fileName: string,
106+
reason: string,
107+
error?: unknown,
108+
): void {
109+
const message = `${fileName}: ${reason}`
110+
111+
if (reportedThemeIssues.get(fileName) === message) {
112+
return
113+
}
114+
115+
reportedThemeIssues.set(fileName, message)
116+
117+
if (error) {
118+
console.warn(`[theme] ${message}`, error)
119+
return
120+
}
121+
122+
console.warn(`[theme] ${message}`)
123+
}
124+
103125
function isThemeType(value: unknown): value is ThemeType {
104126
return value === 'light' || value === 'dark'
105127
}
@@ -114,7 +136,7 @@ function isStringRecord(value: unknown): value is Record<string, string> {
114136

115137
function parseThemeFile(raw: unknown, fileName: string): ThemeFile | null {
116138
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
117-
log('theme:invalid-file', new Error(`Invalid JSON object in ${fileName}`))
139+
reportThemeIssue(fileName, 'Invalid JSON object')
118140
return null
119141
}
120142

@@ -127,30 +149,32 @@ function parseThemeFile(raw: unknown, fileName: string): ThemeFile | null {
127149
}
128150

129151
if (typeof data.name !== 'string' || !data.name.trim()) {
130-
log('theme:invalid-file', new Error(`Invalid name in ${fileName}`))
152+
reportThemeIssue(fileName, 'Invalid name')
131153
return null
132154
}
133155

134156
if (!isThemeType(data.type)) {
135-
log('theme:invalid-file', new Error(`Invalid type in ${fileName}`))
157+
reportThemeIssue(fileName, 'Invalid type')
136158
return null
137159
}
138160

139161
if (data.author !== undefined && typeof data.author !== 'string') {
140-
log('theme:invalid-file', new Error(`Invalid author in ${fileName}`))
162+
reportThemeIssue(fileName, 'Invalid author')
141163
return null
142164
}
143165

144166
if (data.colors !== undefined && !isStringRecord(data.colors)) {
145-
log('theme:invalid-file', new Error(`Invalid colors in ${fileName}`))
167+
reportThemeIssue(fileName, 'Invalid colors')
146168
return null
147169
}
148170

149171
if (data.editorColors !== undefined && !isStringRecord(data.editorColors)) {
150-
log('theme:invalid-file', new Error(`Invalid editorColors in ${fileName}`))
172+
reportThemeIssue(fileName, 'Invalid editorColors')
151173
return null
152174
}
153175

176+
reportedThemeIssues.delete(fileName)
177+
154178
return {
155179
name: data.name.trim(),
156180
author: data.author,
@@ -211,7 +235,7 @@ function readThemeFromFile(
211235
return parseThemeFile(parsed, fileName)
212236
}
213237
catch (error) {
214-
log('theme:read-file', error)
238+
reportThemeIssue(fileName, 'Failed to read or parse JSON', error)
215239
return null
216240
}
217241
}

src/renderer/composables/useTheme.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ const currentCustomTheme = ref<ThemeFile | null>(null)
2626
const isDark = computed(() => resolvedThemeType.value === 'dark')
2727

2828
let isInitialized = false
29+
let isThemeReloadInProgress = false
30+
let hasPendingThemeReload = false
31+
32+
function persistThemePreference(id: string): void {
33+
if (store.preferences.get('theme') === id) {
34+
return
35+
}
36+
37+
store.preferences.set('theme', id)
38+
}
2939

3040
function isBuiltInTheme(id: string): id is BuiltInThemeId {
3141
return BUILT_IN_THEMES.has(id as BuiltInThemeId)
@@ -135,7 +145,7 @@ function applyBuiltInTheme(id: BuiltInThemeId): void {
135145
resolvedThemeType.value = getBuiltInThemeType(id)
136146
currentThemeId.value = id
137147

138-
store.preferences.set('theme', id)
148+
persistThemePreference(id)
139149
}
140150

141151
async function applyCustomTheme(id: string): Promise<boolean> {
@@ -151,7 +161,7 @@ async function applyCustomTheme(id: string): Promise<boolean> {
151161
currentThemeId.value = id
152162

153163
applyCustomThemeStyle(theme)
154-
store.preferences.set('theme', id)
164+
persistThemePreference(id)
155165

156166
return true
157167
}
@@ -205,6 +215,28 @@ async function handleThemesChanged(): Promise<void> {
205215
await setTheme(selectedId)
206216
}
207217

218+
async function processThemeReloadQueue(): Promise<void> {
219+
if (isThemeReloadInProgress) {
220+
hasPendingThemeReload = true
221+
return
222+
}
223+
224+
isThemeReloadInProgress = true
225+
226+
try {
227+
do {
228+
hasPendingThemeReload = false
229+
await handleThemesChanged()
230+
} while (hasPendingThemeReload)
231+
}
232+
catch (error) {
233+
console.error('Failed to process theme updates', error)
234+
}
235+
finally {
236+
isThemeReloadInProgress = false
237+
}
238+
}
239+
208240
function getEditorThemeName(): string {
209241
const baseTheme
210242
= resolvedThemeType.value === 'dark' ? DARK_EDITOR_THEME : LIGHT_EDITOR_THEME
@@ -238,7 +270,7 @@ async function initTheme(): Promise<void> {
238270
}
239271

240272
function onThemeChanged() {
241-
void handleThemesChanged()
273+
void processThemeReloadQueue()
242274
}
243275

244276
watch(

0 commit comments

Comments
 (0)