Compare commits

..

4 Commits

Author SHA1 Message Date
peshomir 88e8b9f273 Bump version 2025-07-14 12:36:33 +03:00
peshomir d7ec66131d Context for known donation history error 2025-07-14 12:26:54 +03:00
peshomir bb36014f55 Error reporting improvements
Arbitraty context can be added to reports of known errors by wrapping the problematic code in the debugWithContext function
2025-07-14 12:26:30 +03:00
peshomir d83d50b5ae Patch for update 2.09.0 2025-07-14 12:11:21 +03:00
7 changed files with 86 additions and 40 deletions

View File

@ -9,7 +9,8 @@ export function minifyCode(/** @type {string} */ script) {
script = "()=>{" + script + "}";
const output = UglifyJS.minify(script, {
compress: false,
mangle: false
mangle: false,
output: { comments: /\.{3}/ } // preserve `/*...*/` comments
});
if (output.error) throw output.error;
if (output.warnings) throw (output.warnings);
@ -17,6 +18,8 @@ export function minifyCode(/** @type {string} */ script) {
let code = output.code;
if (code.endsWith(";")) code = code.slice(0, -1);
code = code.slice(5, -1); // unwrap from function
// to allow finding a function without having to include all of its code:
code = code.split("/*...*/")[0].trim();
return code;
}
@ -96,7 +99,6 @@ class ModUtils {
if (match === "__L") match = "___localizer" + (++localizerCount);
return groups.hasOwnProperty(match) ? "$" + groups[match] : match;
});
//console.log(replacementString);
let expressionMatchResult;
try { expressionMatchResult = this.replaceOne(expression, replacementString); }
catch (e) {
@ -159,6 +161,7 @@ class ModUtils {
}
replaceCode(/** @type {string} */ code, /** @type {string} */ replacement, /** @type {BaseOptions=} */ options) {
replacement = replacement.split("/*...*/")[0];
return this.replaceRawCode(minifyCode(code), replacement, options?.dictionary);
}

View File

@ -7,10 +7,33 @@ export default (/** @type {ModUtils} */ modUtils) => {
modUtils.insertCode(
`window.removeEventListener("error", err);
msg = e.lineno + " " + e.colno + "|" + getStack(e); /* here */`,
`__fx.utils.reportError(e, msg);
`__fx.reportError(e, msg);
return alert("Error:\\n" + e.filename + " " + e.lineno + " " + e.colno + " " + e.message);`
)
// Hovering tooltip
modUtils.insertCode(`/* here */
this.click = function(lK, lL, a1m) {
var gy = bL.gz(lK);
var h0 = bL.h1(lL);
var eV = bL.f7(gy, h0);
var eN = bL.eZ(eV);
if (!bL.isInMapBounds(gy, h0)) { return false; }
var a5C = (Device.a1.largeUIEnabled() ? 0.025 : 0.0144) * h___.hu;
var dp = performance.now();
if (Math.abs(lK - a4t) > a5C || Math.abs(lL - a4u) > a5C || dp > time + 500) { return false; }
time = dp;
if (a1m) { a5D(lK, lL, eN); return false; }
/*...*/}`,
`__fx.hoveringTooltip.display = function(mouseX, mouseY) {
var coordX = bL.gz(mouseX), coordY = bL.h1(mouseY),
coord = bL.f7(coordX, coordY), point = bL.eZ(coord);
// if (coordX < 0 || coordY < 0) return;
if (bL.isInMapBounds(coordX, coordY)) (function(lK, lL, eN) {
a5D(lK, lL, eN)
}(mouseX, mouseY, point))
}`)
modUtils.waitForMinification(() => applyPatches(modUtils))
}
//export const requiredVariables = ["game", "playerId", "playerData", "rawPlayerNames", "gIsSingleplayer", "playerTerritories"];
@ -287,16 +310,6 @@ canvas.font=aY.g0.g1(1,fontSize),canvas.fillStyle="rgba("+gR+","+tD+","+hj+",0.6
}
{ // Hovering tooltip
replaceRawCode("this.click=function(gG,gH,uH){var fd=an.fe(gG),ff=an.fg(gH),fh=an.fi(fd,ff),fj=an.fk(fh);if(!an.fl(fd,ff))return!1;var fd=(bB.d3.isUIZoomEnabled()?.025:.0144)*aO.g4,ff=performance.now();if(Math.abs(gG-wK)>fd||Math.abs(gH-wL)>fd||dg+500<ff)return!1;if(dg=ff,uH&&!function(gG,gH,fj){a3.ek(fj)||-1===(gG=ao.fr.wq(gG,gH))?l.wp(fj):l.wr(gG)}(gG,gH,fj),",
`__fx.hoveringTooltip.display = function(mouseX, mouseY) {
var coordX = an.fe(mouseX), coordY = an.fg(mouseY),
coord = an.fi(coordX, coordY), point = an.fk(coord);
if (coordX < 0 || coordY < 0) return;
(function(gG, gH, fj) {
a3.ek(fj) || -1 === (gG = ao.fr.wq(gG, gH)) ? l.wp(fj) : l.wr(gG)
}(mouseX, mouseY, point))
}
this.click=function(gG,gH,uH){var fd=an.fe(gG),ff=an.fg(gH),fh=an.fi(fd,ff),fj=an.fk(fh);if(!an.fl(fd,ff))return!1;fd=(bB.d3.isUIZoomEnabled()?.025:.0144)*aO.g4,ff=performance.now();if(Math.abs(gG-wK)>fd||Math.abs(gH-wL)>fd||dg+500<ff)return!1;if(dg=ff,uH&&!function(gG,gH,fj){a3.ek(fj)||-1===(gG=ao.fr.wq(gG,gH))?l.wp(fj):l.wr(gG)}(gG,gH,fj),`)
replaceRawCode("aK.nH=(window.devicePixelRatio||1)*aEr,",
`aK.nH = (window.devicePixelRatio || 1) * aEr, __fx.hoveringTooltip.canvasPixelScale = aK.nH,`)
}

40
src/debugging.js 100644
View File

@ -0,0 +1,40 @@
import { getVar } from "./gameInterface.js";
let debugContext = null;
export function reportError(e, message) {
function tryGetVar(name) {
try { return getVar(name) }
catch (error) { return error.toString(); }
}
message = e.filename + " " + e.lineno + " " + e.colno + " " + e.message + "\n" + message;
fetch("https://fx.peshomir.workers.dev/stats/errors", {
body: JSON.stringify({
message,
context: {
debug: debugContext,
gameState: tryGetVar("gameState"),
singleplayer: tryGetVar("gIsSingleplayer"),
swState: navigator.serviceWorker?.controller?.state,
location: window.location.toString(),
userAgent: navigator.userAgent,
dictionary: JSON.stringify(dictionary),
buildTimestamp,
scripts: Array.from(document.scripts).map(s => s.src)
}
}),
method: "POST"
}).catch(e => alert("Failed to report error: " + e));
}
export function debugWithContext(callback, context) {
try {
return callback();
} catch (error) {
debugContext = context;
setTimeout(() => {
if (debugContext !== null) debugContext = null;
});
throw error;
}
}

View File

@ -1,6 +1,7 @@
import WindowManager from "./windowManager.js";
import { getVar } from "./gameInterface.js";
import { escapeHtml } from "./utils.js";
import { debugWithContext } from "./debugging.js";
WindowManager.add({
name: "donationHistory",
@ -16,10 +17,16 @@ const donationsTracker = new (function(){
this.donationHistory = Array(512);
// fill the array with empty arrays with length of 3
//for (var i = 0; i < 512; i++) this.donationHistory.push([]); // not needed as .reset is called on game start
let resetCalled = false;
this.getHistoryOf = function (playerID) {
return this.donationHistory[playerID].toReversed();
return debugWithContext(() => this.donationHistory[playerID].toReversed(), {
playerID,
resetCalled,
type: typeof this.donationHistory[playerID],
isArray: Array.isArray(this.donationHistory[playerID])
});
}
this.reset = function() { for (var i = 0; i < 512; i++) this.donationHistory[i] = []; };
this.reset = function() { resetCalled = true; for (var i = 0; i < 512; i++) this.donationHistory[i] = []; };
this.logDonation = function(senderID, receiverID, amount) {
const donationInfo = [senderID, receiverID, amount];
this.donationHistory[receiverID].push(donationInfo);

View File

@ -22,22 +22,5 @@ function textStyleBasedOnDensity(playerID) {
const playerBalances = getVar("playerBalances"), playerTerritories = getVar("playerTerritories");
return `hsl(${playerBalances[playerID] / (playerTerritories[playerID] * 1.5)}, 100%, 50%, 1)`;
}
function reportError(e, message) {
message = e.filename + " " + e.lineno + " " + e.colno + " " + e.message + "\n" + message;
fetch("https://fx.peshomir.workers.dev/stats/errors", {
body: JSON.stringify({
message,
context: {
swState: navigator.serviceWorker?.controller?.state,
location: window.location.toString(),
userAgent: navigator.userAgent,
dictionary,
buildTimestamp,
scripts: Array.from(document.scripts).map(s => s.src)
}
}),
method: "POST"
}).catch(e => alert("Failed to report error: " + e));
}
export default { getMaxTroops, getDensity, isPointInRectangle, fillTextMultiline, textStyleBasedOnDensity, reportError }
export default { getMaxTroops, getDensity, isPointInRectangle, fillTextMultiline, textStyleBasedOnDensity }

View File

@ -23,6 +23,7 @@ import hoveringTooltip from "./hoveringTooltip.js";
import { keybindFunctions, keybindHandler, mobileKeybinds } from "./keybinds.js";
import customLobby from './customLobby.js';
import { displayChangelog } from './changelog.js';
import { reportError } from './debugging.js';
const savedVersion = localStorage.getItem("fx_version");
if (savedVersion !== version) {
@ -42,6 +43,7 @@ __fx.keybindFunctions = keybindFunctions;
__fx.keybindHandler = keybindHandler;
__fx.mobileKeybinds = mobileKeybinds;
__fx.donationsTracker = donationsTracker;
__fx.reportError = reportError;
__fx.playerList = playerList;
__fx.hoveringTooltip = hoveringTooltip;
__fx.clanFilter = clanFilter;

View File

@ -1,10 +1,8 @@
{
"version": "0.6.10",
"lastUpdated": "Jul 8",
"version": "0.6.11",
"lastUpdated": "Jul 14",
"changes": [
"Fixed floating point number imprecision when configuring keybinds",
"Minor improvements to the keybinds settings menu",
"Added automatic error reporting to simplify bug identification and removal",
"The BetterTT density style (a value from 0 to 150) is now the default way that density is displayed. The percentage style will remain as is it for players who have already used FX Client, but you can change it in the settings at any time."
"Fix for game update 2.09.0",
"Error reporting improvements"
]
}