819caea65a
Security fixes: - Replace Caesar cipher with electron.safeStorage for account credentials - Validate shell.openExternal URLs (allow only http/https protocols) - Remove rejectUnauthorized:false from all HTTPS calls - Add redirect domain validation to auto-updater - Fix XSS in matchmaker popup (innerHTML → textContent/createTextNode) - Add IPC config key whitelist to prevent arbitrary store access - Credentials never sent to renderer; decrypted on-demand via IPC Optimizations and cleanup: - Simplify onBeforeRequest from double-registration to single handler - Lazy-init matchmaker popup DOM (defer until first use) - Invalidate game config cache immediately on write, not on flush - Remove unused STANDARD_ASSET_RE and KeybindDef exports - Deduplicate Keybind type (import from config.ts) - Replace custom hasOwn wrapper with Object.hasOwn Bug fix: - Stop Krunker's global keydown handler from eating keystrokes in alt manager input fields (stopPropagation) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
69 lines
2.2 KiB
TypeScript
69 lines
2.2 KiB
TypeScript
// ── Shared preload utilities ──
|
|
// Common types, helpers, and constants used across preload modules.
|
|
|
|
// ── Shared interfaces ──
|
|
|
|
export interface SavedConsole {
|
|
log: (...args: unknown[]) => void;
|
|
warn: (...args: unknown[]) => void;
|
|
error: (...args: unknown[]) => void;
|
|
}
|
|
|
|
// ── HTML escaping ──
|
|
|
|
const HTML_ESCAPE_MAP: Record<string, string> = {
|
|
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
|
|
};
|
|
|
|
export function escapeHtml(s: string): string {
|
|
return s.replace(/[&<>"']/g, c => HTML_ESCAPE_MAP[c]);
|
|
}
|
|
|
|
// ── Chat message injection ──
|
|
// Creates messages in #chatHolder inside a persistent #kpcMessageHolder div.
|
|
// timeout=0 means the message is persistent (not auto-removed).
|
|
|
|
export function genChatMsg(text: string, timeout = 2.25): HTMLElement | null {
|
|
const chatHolder = document.getElementById('chatHolder');
|
|
if (!chatHolder) return null;
|
|
if (!document.getElementById('kpcMessageHolder')) {
|
|
chatHolder.insertAdjacentHTML('afterbegin', '<div id="kpcMessageHolder"></div>');
|
|
}
|
|
const holder = document.getElementById('kpcMessageHolder')!;
|
|
holder.insertAdjacentHTML('beforeend',
|
|
'<div class="chatHolder_kpc"><div class="chatItem_kpc"><span class="chatMsg_kpc">' +
|
|
escapeHtml(text) + '</span></div></div>');
|
|
const elem = holder.lastElementChild as HTMLElement;
|
|
if (timeout !== 0) {
|
|
setTimeout(() => { elem.remove(); }, timeout * 1000);
|
|
}
|
|
return elem;
|
|
}
|
|
|
|
// ── Filename sanitisation ──
|
|
|
|
export function sanitizeFilename(name: string): string {
|
|
return name.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
}
|
|
|
|
// ── Shared CSS constants ──
|
|
|
|
export const DEATH_ANIM_BLOCK_ID = 'kpc-animationBlock';
|
|
export const DEATH_ANIM_BLOCK_CSS =
|
|
'.death-ui-bottom, .death-ui-bottom-empty { animation: none !important; transition: none !important; }';
|
|
|
|
/** Inject or remove the death screen animation block style element. */
|
|
export function setDeathAnimBlock(enabled: boolean): void {
|
|
let el = document.getElementById(DEATH_ANIM_BLOCK_ID);
|
|
if (enabled) {
|
|
if (!el) {
|
|
el = document.createElement('style');
|
|
el.id = DEATH_ANIM_BLOCK_ID;
|
|
el.textContent = DEATH_ANIM_BLOCK_CSS;
|
|
document.head.appendChild(el);
|
|
}
|
|
} else if (el) {
|
|
el.remove();
|
|
}
|
|
}
|