Skip to content

Commit b1aa14c

Browse files
authored
fix(pace-caret): prevent null dereference in update() (@byseif21) (monkeytypegame#7226)
### Description * `update()` could hit a race where settings became `null` between checks, causing a`TypeError` at runtime. * Async callbacks (setTimeout) accessed the global settings after it was cleared, leading to runaway errors. <img width="1887" height="755" alt="Screenshot 2025-12-12 135316" src="https://github.com/user-attachments/assets/ba6bd13c-c052-4870-ba7c-cfeae916b6dc" /> **fix** settings once (currentSettings) at the start of update() and use that for all property access and scheduling, so the loop never touches a null/stale reference.
1 parent e515506 commit b1aa14c

File tree

1 file changed

+15
-8
lines changed

1 file changed

+15
-8
lines changed

frontend/src/ts/test/pace-caret.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,12 @@ export async function init(): Promise<void> {
130130
}
131131

132132
export async function update(expectedStepEnd: number): Promise<void> {
133-
if (settings === null || !TestState.isActive || TestState.resultVisible) {
133+
const currentSettings = settings;
134+
if (
135+
currentSettings === null ||
136+
!TestState.isActive ||
137+
TestState.resultVisible
138+
) {
134139
return;
135140
}
136141

@@ -146,8 +151,8 @@ export async function update(expectedStepEnd: number): Promise<void> {
146151
const duration = absoluteStepEnd - now;
147152

148153
caret.goTo({
149-
wordIndex: settings.currentWordIndex,
150-
letterIndex: settings.currentLetterIndex,
154+
wordIndex: currentSettings.currentWordIndex,
155+
letterIndex: currentSettings.currentLetterIndex,
151156
isLanguageRightToLeft: TestState.isLanguageRightToLeft,
152157
isDirectionReversed: TestState.isDirectionReversed,
153158
animate: true,
@@ -157,12 +162,14 @@ export async function update(expectedStepEnd: number): Promise<void> {
157162
},
158163
});
159164

160-
// Normal case - schedule next step
161-
settings.timeout = setTimeout(
165+
currentSettings.timeout = setTimeout(
162166
() => {
163-
update(expectedStepEnd + (settings?.spc ?? 0) * 1000).catch(() => {
164-
settings = null;
165-
});
167+
if (settings !== currentSettings) return;
168+
update(expectedStepEnd + (currentSettings.spc ?? 0) * 1000).catch(
169+
() => {
170+
if (settings === currentSettings) settings = null;
171+
},
172+
);
166173
},
167174
Math.max(0, duration),
168175
);

0 commit comments

Comments
 (0)