generated from nhcarrigan/template
chore: audit frontend error reporting to exclude expected behaviours #79
@@ -1224,9 +1224,12 @@ export const GameProvider = ({
|
|||||||
) {
|
) {
|
||||||
signatureReference.current = null;
|
signatureReference.current = null;
|
||||||
localStorage.removeItem("elysium_save_signature");
|
localStorage.removeItem("elysium_save_signature");
|
||||||
} else {
|
|
||||||
logError("auto_save", error_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Network failures during background auto-save are expected on
|
||||||
|
* flaky connections — the next tick will retry, so no telemetry needed
|
||||||
|
*/
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1254,10 +1257,9 @@ export const GameProvider = ({
|
|||||||
}
|
}
|
||||||
await reloadReference.current();
|
await reloadReference.current();
|
||||||
}).
|
}).
|
||||||
catch((error_: unknown) => {
|
catch(() => {
|
||||||
logError("auto_prestige", error_);
|
|
||||||
|
|
||||||
/* Silently ignore — will retry next tick */
|
/* Silently ignore — eligibility is re-checked every tick */
|
||||||
}).
|
}).
|
||||||
finally(() => {
|
finally(() => {
|
||||||
isAutoPrestigingReference.current = false;
|
isAutoPrestigingReference.current = false;
|
||||||
@@ -1307,11 +1309,18 @@ export const GameProvider = ({
|
|||||||
});
|
});
|
||||||
}).
|
}).
|
||||||
catch((error_: unknown) => {
|
catch((error_: unknown) => {
|
||||||
logError("auto_boss", error_);
|
|
||||||
const message
|
const message
|
||||||
= error_ instanceof Error
|
= error_ instanceof Error
|
||||||
? error_.message
|
? error_.message
|
||||||
: String(error_);
|
: String(error_);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "Boss is not currently available" is an expected race condition
|
||||||
|
* in the tick loop — suppress telemetry for this case only
|
||||||
|
*/
|
||||||
|
if (message !== "Boss is not currently available") {
|
||||||
|
logError("auto_boss", error_);
|
||||||
|
}
|
||||||
setAutoBossError(message);
|
setAutoBossError(message);
|
||||||
setState((previous) => {
|
setState((previous) => {
|
||||||
if (previous === null) {
|
if (previous === null) {
|
||||||
@@ -1709,114 +1718,104 @@ export const GameProvider = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const startExploration = useCallback(async(areaId: string) => {
|
const startExploration = useCallback(async(areaId: string) => {
|
||||||
try {
|
const response = await startExplorationApi({ areaId });
|
||||||
const response = await startExplorationApi({ areaId });
|
setState((previous) => {
|
||||||
|
if (previous?.exploration === undefined) {
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...previous,
|
||||||
|
exploration: {
|
||||||
|
...previous.exploration,
|
||||||
|
areas: previous.exploration.areas.map((a) => {
|
||||||
|
return a.id === areaId
|
||||||
|
? {
|
||||||
|
...a,
|
||||||
|
endsAt: response.endsAt,
|
||||||
|
status: "in_progress" as const,
|
||||||
|
}
|
||||||
|
: a;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const collectExploration = useCallback(
|
||||||
|
async(areaId: string): Promise<ExploreCollectResponse> => {
|
||||||
|
const result = await collectExplorationApi({ areaId });
|
||||||
setState((previous) => {
|
setState((previous) => {
|
||||||
if (previous?.exploration === undefined) {
|
if (previous?.exploration === undefined) {
|
||||||
return previous;
|
return previous;
|
||||||
}
|
}
|
||||||
|
let materials = [ ...previous.exploration.materials ];
|
||||||
|
|
||||||
|
// Apply material drops from the random loot roll
|
||||||
|
for (const drop of result.materialsFound) {
|
||||||
|
const existing = materials.find((mat) => {
|
||||||
|
return mat.materialId === drop.materialId;
|
||||||
|
});
|
||||||
|
if (existing === undefined) {
|
||||||
|
materials = [
|
||||||
|
...materials,
|
||||||
|
{ materialId: drop.materialId, quantity: drop.quantity },
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
materials = materials.map((mat) => {
|
||||||
|
return mat.materialId === drop.materialId
|
||||||
|
? { ...mat, quantity: mat.quantity + drop.quantity }
|
||||||
|
: mat;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply material from event (if any)
|
||||||
|
const materialGained = result.event?.materialGained;
|
||||||
|
if (materialGained !== null && materialGained !== undefined) {
|
||||||
|
const { materialId, quantity } = materialGained;
|
||||||
|
const existing = materials.find((mat) => {
|
||||||
|
return mat.materialId === materialId;
|
||||||
|
});
|
||||||
|
if (existing === undefined) {
|
||||||
|
materials = [ ...materials, { materialId, quantity } ];
|
||||||
|
} else {
|
||||||
|
materials = materials.map((mat) => {
|
||||||
|
return mat.materialId === materialId
|
||||||
|
? { ...mat, quantity: mat.quantity + quantity }
|
||||||
|
: mat;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...previous,
|
...previous,
|
||||||
exploration: {
|
exploration: {
|
||||||
...previous.exploration,
|
...previous.exploration,
|
||||||
areas: previous.exploration.areas.map((a) => {
|
areas: previous.exploration.areas.map((a) => {
|
||||||
return a.id === areaId
|
return a.id === areaId
|
||||||
? {
|
? { ...a, completedOnce: true, status: "available" as const }
|
||||||
...a,
|
|
||||||
endsAt: response.endsAt,
|
|
||||||
status: "in_progress" as const,
|
|
||||||
}
|
|
||||||
: a;
|
: a;
|
||||||
}),
|
}),
|
||||||
|
materials: materials,
|
||||||
|
},
|
||||||
|
player: {
|
||||||
|
...previous.player,
|
||||||
|
totalGoldEarned:
|
||||||
|
previous.player.totalGoldEarned
|
||||||
|
+ Math.max(0, result.event?.goldChange ?? 0),
|
||||||
|
},
|
||||||
|
resources: {
|
||||||
|
...previous.resources,
|
||||||
|
essence:
|
||||||
|
previous.resources.essence + (result.event?.essenceChange ?? 0),
|
||||||
|
gold: Math.max(
|
||||||
|
0,
|
||||||
|
previous.resources.gold + (result.event?.goldChange ?? 0),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
} catch (error_: unknown) {
|
return result;
|
||||||
logError("start_exploration", error_);
|
|
||||||
throw error_;
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const collectExploration = useCallback(
|
|
||||||
async(areaId: string): Promise<ExploreCollectResponse> => {
|
|
||||||
try {
|
|
||||||
const result = await collectExplorationApi({ areaId });
|
|
||||||
setState((previous) => {
|
|
||||||
if (previous?.exploration === undefined) {
|
|
||||||
return previous;
|
|
||||||
}
|
|
||||||
let materials = [ ...previous.exploration.materials ];
|
|
||||||
|
|
||||||
// Apply material drops from the random loot roll
|
|
||||||
for (const drop of result.materialsFound) {
|
|
||||||
const existing = materials.find((mat) => {
|
|
||||||
return mat.materialId === drop.materialId;
|
|
||||||
});
|
|
||||||
if (existing === undefined) {
|
|
||||||
materials = [
|
|
||||||
...materials,
|
|
||||||
{ materialId: drop.materialId, quantity: drop.quantity },
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
materials = materials.map((mat) => {
|
|
||||||
return mat.materialId === drop.materialId
|
|
||||||
? { ...mat, quantity: mat.quantity + drop.quantity }
|
|
||||||
: mat;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply material from event (if any)
|
|
||||||
const materialGained = result.event?.materialGained;
|
|
||||||
if (materialGained !== null && materialGained !== undefined) {
|
|
||||||
const { materialId, quantity } = materialGained;
|
|
||||||
const existing = materials.find((mat) => {
|
|
||||||
return mat.materialId === materialId;
|
|
||||||
});
|
|
||||||
if (existing === undefined) {
|
|
||||||
materials = [ ...materials, { materialId, quantity } ];
|
|
||||||
} else {
|
|
||||||
materials = materials.map((mat) => {
|
|
||||||
return mat.materialId === materialId
|
|
||||||
? { ...mat, quantity: mat.quantity + quantity }
|
|
||||||
: mat;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...previous,
|
|
||||||
exploration: {
|
|
||||||
...previous.exploration,
|
|
||||||
areas: previous.exploration.areas.map((a) => {
|
|
||||||
return a.id === areaId
|
|
||||||
? { ...a, completedOnce: true, status: "available" as const }
|
|
||||||
: a;
|
|
||||||
}),
|
|
||||||
materials: materials,
|
|
||||||
},
|
|
||||||
player: {
|
|
||||||
...previous.player,
|
|
||||||
totalGoldEarned:
|
|
||||||
previous.player.totalGoldEarned
|
|
||||||
+ Math.max(0, result.event?.goldChange ?? 0),
|
|
||||||
},
|
|
||||||
resources: {
|
|
||||||
...previous.resources,
|
|
||||||
essence:
|
|
||||||
previous.resources.essence + (result.event?.essenceChange ?? 0),
|
|
||||||
gold: Math.max(
|
|
||||||
0,
|
|
||||||
previous.resources.gold + (result.event?.goldChange ?? 0),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
} catch (error_: unknown) {
|
|
||||||
logError("collect_exploration", error_);
|
|
||||||
throw error_;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@@ -1960,11 +1959,20 @@ export const GameProvider = ({
|
|||||||
});
|
});
|
||||||
setBattleResult({ bossName: boss.name, result: result });
|
setBattleResult({ bossName: boss.name, result: result });
|
||||||
} catch (error_: unknown) {
|
} catch (error_: unknown) {
|
||||||
logError("challenge_boss", error_);
|
const bossErrorMessage
|
||||||
setBossError(
|
= error_ instanceof Error
|
||||||
error_ instanceof Error
|
|
||||||
? error_.message
|
? error_.message
|
||||||
: "Failed to challenge boss",
|
: "Failed to challenge boss";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "Boss is not currently available" is an expected server rejection
|
||||||
|
* (race condition between UI state and server state) — suppress telemetry
|
||||||
|
*/
|
||||||
|
if (bossErrorMessage !== "Boss is not currently available") {
|
||||||
|
logError("challenge_boss", error_);
|
||||||
|
}
|
||||||
|
setBossError(
|
||||||
|
bossErrorMessage,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [ forceSync ]);
|
}, [ forceSync ]);
|
||||||
|
|||||||
Reference in New Issue
Block a user