From cedb501c7b94e13cd2aa13c845f92f07a12f8d08 Mon Sep 17 00:00:00 2001 From: KraXen72 Date: Tue, 20 Jun 2023 22:54:25 +0200 Subject: [PATCH] feat: cleanup in all files, fixed userscript meta & advSlider --- src/global.d.ts | 4 ++++ src/main.ts | 45 +++++-------------------------------- src/preload.ts | 50 ++++++++++++++++++++++-------------------- src/resourceswapper.ts | 4 ---- src/settingsui.ts | 21 +++++++----------- src/userscripts.ts | 44 +++++++++---------------------------- 6 files changed, 54 insertions(+), 114 deletions(-) diff --git a/src/global.d.ts b/src/global.d.ts index 612d0f3..84a2f40 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -41,6 +41,10 @@ interface Window { errAlert: Function; OffCliV: boolean; getGameActivity: Function + windows: [ { + settingType: 'basic' | 'advanced'; + toggleType: Function; + }, ...Object[]]; } /* diff --git a/src/main.ts b/src/main.ts index 6c3799b..1ea4689 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,17 +7,6 @@ import Swapper from './resourceswapper'; /// -// Credits / mentions (if not mentioned on github) -/* - * Gato/creepycats - Gatoclient - * LukeTheDuke - Gatoclient-lite - * Mixaz and IDKR team - https://github.com/idkr-client/idkr - * wa#3991 / paintingofblue - matchmaker implementation - * Commander/asger-finding (AKC client) - resource swapper implementation - * Giant - JANREX client - * Tae - logo for the client <3 - */ - const docsPath = app.getPath('documents'); const swapperPath = pathJoin(docsPath, 'Crankshaft/swapper'); const settingsPath = pathJoin(docsPath, 'Crankshaft/settings.json'); @@ -58,8 +47,6 @@ const settingsSkeleton = { matchmaker_minRemainingTime: 120 }; -// export type settingsKeys = keyof typeof settingsSkeleton - if (!existsSync(swapperPath)) mkdirSync(swapperPath, { recursive: true }); if (!existsSync(userscriptsPath)) mkdirSync(userscriptsPath, { recursive: true }); if (!existsSync(userscriptTrackerPath)) writeFileSync(userscriptTrackerPath, '{}', { encoding: 'utf-8' }); @@ -67,15 +54,13 @@ if (!existsSync(userscriptTrackerPath)) writeFileSync(userscriptTrackerPath, '{} // Before we can read the settings, we need to make sure they exist, if they don't, then we create a template if (!existsSync(settingsPath)) writeFileSync(settingsPath, JSON.stringify(settingsSkeleton, null, 2), { encoding: 'utf-8', flag: 'wx' }); - -// Read settings to apply them to the command line arguments const userPrefs = settingsSkeleton; Object.assign(userPrefs, JSON.parse(readFileSync(settingsPath, { encoding: 'utf-8' }))); // convert legacy settings files to newer formats let modifiedSettings = false; -// fullscreen was a true/false, now it's "windowed", "fullscreen" or "borderless" +// initially, fullscreen was a true/false, now it's "windowed", "fullscreen" or "borderless" if (typeof userPrefs.fullscreen === 'boolean') { modifiedSettings = true; if (userPrefs.fullscreen === true) userPrefs.fullscreen = 'fullscreen'; else userPrefs.fullscreen = 'windowed'; @@ -84,18 +69,14 @@ if (typeof userPrefs.fullscreen === 'boolean') { // write the new settings format to the settings.json file right after the conversion if (modifiedSettings) writeFileSync(settingsPath, JSON.stringify(userPrefs, null, 2), { encoding: 'utf-8' }); - -// Window definitions -/* eslint-disable init-declarations */ let mainWindow: BrowserWindow; let socialWindowReference: BrowserWindow; -/* eslint-disable init-declarations */ ipcMain.on('logMainConsole', (event, data) => { console.log(data); }); // send usercript path to preload -ipcMain.on('preload_requests_userscriptPath', () => { - mainWindow.webContents.send('main_sends_userscriptPath', userscriptsPath, __dirname); +ipcMain.on('initializeUserscripts', () => { + mainWindow.webContents.send('main_initializes_userscripts', userscriptsPath, __dirname); }); // initial request of settings to populate the settingsUI @@ -162,7 +143,6 @@ function customGenericWin(url: string, providedMenuTemplate: (MenuItemConstructo ]); } - const thisMenu = Menu.buildFromTemplate(providedMenuTemplate); genericWin.setMenu(thisMenu); @@ -253,7 +233,7 @@ app.on('ready', () => { mainWindow.webContents.send('checkForUpdates', app.getVersion()); mainWindow.webContents.send('injectClientCSS', userPrefs, app.getVersion()); // tell preload to inject settingcss and splashcss + other - mainWindow.webContents.on('did-finish-load', () => { mainWindow.webContents.send('main_did-finish-load'); }); + mainWindow.webContents.on('did-finish-load', () => { mainWindow.webContents.send('main_did-finish-load'); }); // only used to updateRPC in preload with real data if (userPrefs.discordRPC) { // eslint-disable-next-line @@ -291,8 +271,6 @@ app.on('ready', () => { mainWindow.loadFile(pathJoin($assets, 'dummy.html')); - // mainWindow.loadURL('https://krunker.io') - if (userPrefs.logDebugToConsole) { console.log('GPU INFO BEGIN'); app.getGPUInfo('complete').then(completeObj => { @@ -328,11 +306,6 @@ app.on('ready', () => { { type: 'separator' }, ...constructDevtoolsSubmenu(mainWindow, userPrefs.alwaysWaitForDevTools || null) ] - - /* - * you can add a relaunch command with: { label: 'Relaunch Client', accelerator: 'F10', click: () => { app.relaunch(); app.exit(); } } - * it is not recommended! - it is prone to cause memory leaks & does not restart the client fully - */ }; if (process.platform !== 'darwin') csMenuTemplate.push({ label: 'About', submenu: aboutSubmenu }); @@ -352,7 +325,6 @@ app.on('ready', () => { const freeSpinHostnames = ['youtube.com', 'twitch.tv', 'twitter.com', 'reddit.com', 'discord.com', 'accounts.google.com', 'instagram.com']; // sanity check, if social window is destroyed but the reference still exists - // eslint-disable-next-line no-void if (typeof socialWindowReference !== 'undefined' && socialWindowReference.isDestroyed()) socialWindowReference = void 0; if (url.includes('https://krunker.io/social.html') && typeof socialWindowReference !== 'undefined') { @@ -396,6 +368,7 @@ app.on('ready', () => { mainWindow.loadURL(url); } else { // for any other link, fall back to creating a custom window with strippedMenu. event.preventDefault(); + console.log(`genericWindow created for ${url}`, socialWindowReference); const genericWin = customGenericWin(url, strippedMenuTemplate); event.newGuest = genericWin; @@ -417,19 +390,13 @@ app.on('ready', () => { } }); - // mainWindow.webContents.on("will-navigate", (event: Event, url: string) => { console.log(url) }) - - // Resource Swapper, thanks idkr if (userPrefs.resourceSwapper) { const CrankshaftSwapInstance = new Swapper(mainWindow, swapperPath); CrankshaftSwapInstance.start(); } }); -/* - * for the 2nd attempt at fixing the memory leak, i am just going to rely on standard electron lifecycle logic - * when all windows close, the app should exit itself - */ +// for the 2nd attempt at fixing the memory leak, i am just going to rely on standard electron lifecycle logic - when all windows close, the app should exit itself // eslint-disable-next-line consistent-return app.on('window-all-closed', () => { if (process.platform !== 'darwin') return app.quit(); // don't quit on mac systems unless user explicitly quits diff --git a/src/preload.ts b/src/preload.ts index b1128c1..7f37a09 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -37,9 +37,6 @@ document.addEventListener('DOMContentLoaded', () => { // Side Menu Settings Thing const settingsSideMenu = document.querySelector('.menuItem[onclick*="showWindow(1)"]'); settingsSideMenu.addEventListener('click', () => { updateSettingsTabs(lastActiveTab, true, true); }); - - // @ts-ignore cba to add it to the window interface - try { window.windows[0].toggleType({ checked: true }); } catch (err) { strippedConsole.warn("couldn't toggle Advanced slider"); } }); ipcRenderer.on('checkForUpdates', async(event, currentVersion) => { @@ -113,9 +110,9 @@ ipcRenderer.on('initDiscordRPC', () => { document.addEventListener('pointerlockchange', updateRPC); // thank God this exists }); -ipcRenderer.on('matchmakerRedirect', (event, _userPrefs: UserPrefs) => fetchGame(_userPrefs)); +ipcRenderer.on('matchmakerRedirect', (_event, _userPrefs: UserPrefs) => fetchGame(_userPrefs)); -ipcRenderer.on('injectClientCSS', (_event, _userPrefs: UserPrefs, version) => { +ipcRenderer.on('injectClientCSS', (_event, _userPrefs: UserPrefs, version: string) => { // eslint-disable-next-line const { matchmaker, matchmaker_F6 } = _userPrefs; @@ -143,6 +140,15 @@ ipcRenderer.on('injectClientCSS', (_event, _userPrefs: UserPrefs, version) => { innerHTML: readFileSync(pathJoin($assets, 'full_logo.svg'), { encoding: 'utf-8' }) }); + const clearSplash = (_observer: MutationObserver) => { + try { + logoSVG.remove(); + _observer.disconnect(); + } catch (e) { + console.log('splash screen was already cleared.'); + } + }; + instructionHider.appendChild(logoSVG); // i am not sure if you should be injecting more elements into a svg element, but it seems to work. feel free to pr a better version tho. @@ -151,16 +157,12 @@ ipcRenderer.on('injectClientCSS', (_event, _userPrefs: UserPrefs, version) => { const observerConfig = { attributes: true, childList: true, subtree: true }; const callback = (mutationList: MutationRecord[], observer: MutationObserver) => { - for (const mutation of mutationList) { - if (mutation.type === 'childList') { - logoSVG.remove(); - observer.disconnect(); - } - } + for (const mutation of mutationList) if (mutation.type === 'childList') clearSplash(observer); }; const observer = new MutationObserver(callback); observer.observe(document.getElementById('instructions'), observerConfig); + document.addEventListener('pointerlockchange', () => { clearSplash(observer); }, { once: true }); } if (hideAds) { @@ -170,9 +172,10 @@ ipcRenderer.on('injectClientCSS', (_event, _userPrefs: UserPrefs, version) => { if (menuTimer) toggleSettingCSS(styleSettingsCSS.menuTimer, 'menuTimer', true); if (quickClassPicker) toggleSettingCSS(styleSettingsCSS.quickClassPicker, 'quickClassPicker', true); if (hideReCaptcha) toggleSettingCSS(styleSettingsCSS.hideReCaptcha, 'hideReCaptcha', true); - if (userscripts) ipcRenderer.send('preload_requests_userscriptPath'); + if (userscripts) ipcRenderer.send('initializeUserscripts'); }); + /** * make sure our setting tab is always called as it should be and has the proper onclick * @param activeTab the tab that should get active class @@ -180,12 +183,14 @@ ipcRenderer.on('injectClientCSS', (_event, _userPrefs: UserPrefs, version) => { * @param coldStart if client tab is selected upon launch of settings themselved, also call renderSettings() */ function updateSettingsTabs(activeTab: number, hookSearch = true, coldStart = false) { - strippedConsole.log('update settings tabs'); const activeClass = 'tabANew'; const settHolder = document.getElementById('settHolder'); - // @ts-ignore - if (window?.windows[0]?.settingsType === 'basic') window.windows[0].toggleType({ checked: true }); + if (window?.windows[0]?.settingType === 'basic') { + window.windows[0].toggleType({ checked: true }); + setTimeout(() => updateSettingsTabs(activeTab, hookSearch, coldStart), 700); + return; + } // FIXME currently, if user clicks too fast, while krunker is still loading, settings might not be hooked - investigate & fix @@ -194,7 +199,7 @@ function updateSettingsTabs(activeTab: number, hookSearch = true, coldStart = fa // eslint-disable-next-line no-param-reassign hookSearch = false; - const settSearchCallback = () => { updateSettingsTabs(0, hookSearch); }; + const settSearchCallback = () => updateSettingsTabs(0, hookSearch); try { try { document.getElementById('settSearch').removeEventListener('input', settSearchCallback); } catch (e) { } @@ -208,14 +213,11 @@ function updateSettingsTabs(activeTab: number, hookSearch = true, coldStart = fa } try { - const advSliderElem = document.querySelector('.advancedSwitch input#typeBtn'); - const advSwitchCallback = () => { - advSliderElem.setAttribute('disabled', 'disabled'); - setTimeout(() => { - advSliderElem.removeAttribute('disabled'); - updateSettingsTabs(0, true); - }, 700); - }; + const advSliderElem: HTMLInputElement = document.querySelector('.advancedSwitch input#typeBtn'); + advSliderElem.disabled = true; + advSliderElem.nextElementSibling.setAttribute('title', 'Crankshaft auto-enables advanced settings mode'); + + const advSwitchCallback = () => updateSettingsTabs(0, true); try { advSliderElem.removeEventListener('change', advSwitchCallback); } catch (e) { } advSliderElem.addEventListener('change', advSwitchCallback); diff --git a/src/resourceswapper.ts b/src/resourceswapper.ts index b629fdf..15cf68a 100644 --- a/src/resourceswapper.ts +++ b/src/resourceswapper.ts @@ -9,16 +9,12 @@ const TARGET_GAME_DOMAIN = 'krunker.io'; */ export default class { - /** Target window. */ private browserWindow: Electron.BrowserWindow; - /** The list of URLs to swap. */ private urls: string[] = []; - /** Has start() been called on the class? */ private started = false; - /** which directory to swap */ private swapDir: string; /** diff --git a/src/settingsui.ts b/src/settingsui.ts index a4e7e46..91814ec 100644 --- a/src/settingsui.ts +++ b/src/settingsui.ts @@ -2,7 +2,7 @@ import { writeFileSync } from 'fs'; import { ipcRenderer } from 'electron'; // add app if crashes import { createElement, haveSameContents, toggleSettingCSS } from './utils'; -import { styleSettingsCSS, classPickerBottom } from './preload'; +import { styleSettingsCSS, classPickerBottom, strippedConsole } from './preload'; import { su } from './userscripts'; import { MATCHMAKER_GAMEMODES, MATCHMAKER_REGIONS } from './matchmaker'; @@ -254,10 +254,9 @@ class SettingElem { /** * update the settings when you change something in the gui - * @param {{elem: Element, callback: 'normal'|Function}} elemAndCb + * @param {{elem: HTMLElement, callback: 'normal'|Function}} elemAndCb */ - // eslint-disable-next-line @typescript-eslint/ban-types - update({ elem, callback }: { elem: Element; callback: 'normal' | 'userscript' | Function; }) { + update({ elem, callback }: { elem: HTMLElement; callback: 'normal' | 'userscript' | Function; }) { if (this.updateKey === '') throw 'Invalid update key'; const target = elem.querySelector('.s-update') as HTMLInputElement; @@ -327,9 +326,7 @@ class SettingElem { } - /** - * this initializes the element and its eventlisteners. - */ + /** this initializes the element and its eventlisteners.*/ get elem() { if (this.#wrapper !== false) return this.#wrapper; // returnt he element if already initialized @@ -356,9 +353,7 @@ class SettingElem { // i am insane for making this -/** - * a settings generation helper. has some skeleton elements and methods that make them. purpose: prevents code duplication - */ +/** a settings generation helper. has some skeleton elements and methods that make them. purpose: prevents code duplication */ const skeleton = { /** make a setting cateogry */ category: (title: string, innerHTML: string, elemClass = 'mainSettings') => ` @@ -460,7 +455,6 @@ export function renderSettings() { { desc: 'Go to the Crankshaft README.md to download some made by the client dev.' }))); } - //
DISCLAIMER
const userscriptSettings: RenderReadySetting[] = su.userscripts .map(userscript => { const obj: RenderReadySetting = { @@ -473,12 +467,13 @@ export function renderSettings() { userscriptReference: userscript, callback: 'userscript' }; - if (userscript.meta) { // render custom metadata if provided in userscrsipt.exported + if (userscript.meta) { // render custom metadata if provided + strippedConsole.log(`${userscript.name} has metadata:`, userscript.meta); const thisMeta = userscript.meta; Object.assign(obj, { title: 'name' in thisMeta && thisMeta.name ? thisMeta.name : userscript.name, desc: `${'desc' in thisMeta && thisMeta.desc ? thisMeta.desc.slice(0, 60) : ''} - ${'author' in thisMeta && thisMeta.author ? `• by ${thisMeta.author}` : ''} + ${'author' in thisMeta && thisMeta.author ? `• ${thisMeta.author}` : ''} ${'version' in thisMeta && thisMeta.version ? `• v${thisMeta.version}` : ''} ${'src' in thisMeta && thisMeta.src ? ` • source` : ''}` }); diff --git a/src/userscripts.ts b/src/userscripts.ts index c3c29c7..05de1a1 100644 --- a/src/userscripts.ts +++ b/src/userscripts.ts @@ -22,12 +22,11 @@ const errAlert = (err: Error, name: string) => { /** class for userscripts */ class Userscript implements IUserscriptInstance { - // stuff we are initialized with name: string; fullpath: string; - content?: string; + content: string; // parsed metadata, unload function and @run-at meta: UserscriptMeta | false; @@ -38,13 +37,11 @@ class Userscript implements IUserscriptInstance { #strictMode: boolean; - #initialized: boolean; - runAt: ('document-start' | 'document-end') = 'document-end'; constructor(props: IUserscript) { + strippedConsole.log('constructor for new userscript', props); this.hasRan = false; - this.#initialized = false; this.#strictMode = false; this.name = props.name; @@ -54,6 +51,7 @@ class Userscript implements IUserscriptInstance { this.unload = false; this.content = readFileSync(this.fullpath, { encoding: 'utf-8' }); + if (this.content.startsWith('"use strict"')) this.#strictMode = true; if (this.content.includes('// ==UserScript==') && this.content.includes('// ==/UserScript==')) { // eslint-disable-next-line const metaParser = require('userscript-meta'); @@ -63,7 +61,7 @@ class Userscript implements IUserscriptInstance { const startLine = chunk.findIndex(line => line.includes('// ==UserScript==')); const endLine = chunk.findIndex(line => line.includes('// ==/UserScript==')); - if (startLine === -1 && endLine !== -1) { + if (startLine !== -1 && endLine !== -1) { chunk = chunk.slice(startLine, endLine + 1).join('\n'); this.meta = metaParser.parse(chunk) as UserscriptMeta; // assume this.meta is not false when parsing @@ -72,11 +70,7 @@ class Userscript implements IUserscriptInstance { * we check if a value isArray and if yes, take the last item in that array as the new value */ - // eslint-disable-next-line init-declarations - let metaKey: keyof UserscriptMeta; - - // @ts-ignore - for ((metaKey) of Object.keys(this.meta)) { + for (const metaKey of Object.keys(this.meta) as Array) { const meta = this.meta[metaKey]; if (Array.isArray(meta)) this.meta[metaKey] = meta[meta.length - 1]; } @@ -86,28 +80,12 @@ class Userscript implements IUserscriptInstance { } } - /** determine if script is in strictmode or no */ - #init() { - if (this.content.startsWith('"use strict"')) this.#strictMode = true; - this.#initialized = true; - - return this.content; - } - - /** return ready-to-run content */ - get #content() { - if (this.#initialized) return this.content; return this.#init(); - } - /** runs the userscript */ load() { - // eslint-disable-next-line no-new-wrappers - const code = String(this.#content); - try { // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-implied-eval - const exported = new Function(code).apply({ + const exported = new Function(this.content).apply({ unload: false, _console: strippedConsole, _css: userscriptToggleCSS @@ -119,8 +97,8 @@ class Userscript implements IUserscriptInstance { if ('unload' in exported) this.unload = exported.unload; } - strippedConsole.log(`%c[cs]${this.#strictMode ? '%c[non-strict]' : '%c[strict]'} %cran %c'${this.name.toString()}' `, - 'color: lightblue; font-weight: bold;', this.#strictMode ? 'color: orange' : 'color: #62dd4f', + strippedConsole.log(`%c[cs]${this.#strictMode ? '%c[strict]' : '%c[non-strict]'} %cran %c'${this.name.toString()}' `, + 'color: lightblue; font-weight: bold;', this.#strictMode ? 'color: #62dd4f' : 'color: orange', 'color: white;', 'color: lightgreen;'); } catch (error) { errAlert(error, this.name); @@ -130,13 +108,11 @@ class Userscript implements IUserscriptInstance { } -ipcRenderer.on('main_sends_userscriptPath', (event, recieved_userscriptsPath: string) => { +ipcRenderer.on('main_initializes_userscripts', (event, recieved_userscriptsPath: string) => { su.userscriptsPath = recieved_userscriptsPath; su.userscriptTrackerPath = pathResolve(su.userscriptsPath, 'tracker.json'); // init the userscripts (read, map and set up tracker) - - // remove get all .js files, map to {name, fullpath} su.userscripts = readdirSync(su.userscriptsPath, { withFileTypes: true }) .filter(entry => entry.name.endsWith('.js')) .map(entry => new Userscript({ name: entry.name, fullpath: pathResolve(su.userscriptsPath, entry.name).toString() })); @@ -153,7 +129,7 @@ ipcRenderer.on('main_sends_userscriptPath', (event, recieved_userscriptsPath: st if (u.runAt === 'document-start') { u.load(); } else { - const callback = () => { u.load(); }; + const callback = () => u.load(); try { document.removeEventListener('DOMContentLoaded', callback); } catch (e) { } document.addEventListener('DOMContentLoaded', callback, { once: true }); }