From 8894bdeb2762a9735cafa80eb076150a7da1658c Mon Sep 17 00:00:00 2001 From: TheBlackParade Date: Wed, 18 Mar 2020 17:27:11 -0500 Subject: [PATCH] Adding cleaner error handling for promises. --- src/error-handling.ts | 42 +++++++++++++++++++ src/game-server.ts | 8 ---- src/main.ts | 2 + src/net/data-parser/client-login-parser.ts | 12 +++--- src/net/data-parser/login-handshake-parser.ts | 4 +- src/net/data-parser/update-server-parser.ts | 2 +- .../data-parser/version-handshake-parser.ts | 4 +- src/plugins/commands/give-item-command.ts | 4 +- .../commands/new-dialogue-test-command.ts | 2 +- .../lumbridge-farm-helpers-plugin.ts | 6 +-- src/plugins/objects/ladders/ladder-plugin.ts | 2 +- src/plugins/quests/cooks-assistant-quest.ts | 2 +- src/plugins/skills/skill-guide-plugin.ts | 2 +- src/world/actor/dialogue.ts | 17 +++++--- .../actor/player/action/dialogue-action.ts | 7 ++-- .../player/action/item-selection-action.ts | 2 +- src/world/actor/player/player.ts | 5 +-- src/world/config/item-data.ts | 2 +- src/world/config/npc-spawn.ts | 2 +- src/world/config/server-config.ts | 2 +- src/world/config/shops.ts | 2 +- 21 files changed, 87 insertions(+), 44 deletions(-) create mode 100644 src/error-handling.ts diff --git a/src/error-handling.ts b/src/error-handling.ts new file mode 100644 index 000000000..7a7ccd045 --- /dev/null +++ b/src/error-handling.ts @@ -0,0 +1,42 @@ +import { logger } from '@runejs/logger/dist/logger'; + +/* + * Error handling! Feel free to add other types of errors or warnings here. :) + */ + +export class WidgetsClosedWarning extends Error { + constructor() { + super(); + this.name = 'WidgetsClosedWarning'; + this.message = 'The active widget was closed before the action could be completed.'; + } +} + +export class ActionsCancelledWarning extends Error { + constructor() { + super(); + this.name = 'ActionsCancelledWarning'; + this.message = 'Pending and active actions were cancelled before they could be completed.'; + } +} + +const warnings = [ + WidgetsClosedWarning, + ActionsCancelledWarning +]; + +export function initErrorHandling(): void { + process.on('unhandledRejection', (error, promise) => { + for(const t of warnings) { + if(error instanceof t) { + logger.warn(`Promise cancelled with warning: ${error.name}`); + return; + } + } + + logger.error(`Unhandled promise rejection from ${promise}, reason: ${error}`); + if(error.hasOwnProperty('stack')) { + logger.error((error as any).stack); + } + }); +} diff --git a/src/game-server.ts b/src/game-server.ts index 1b3be5361..1324c175e 100644 --- a/src/game-server.ts +++ b/src/game-server.ts @@ -95,14 +95,6 @@ export function runGameServer(): void { world.generateFakePlayers(); } - process.on('unhandledRejection', (err, promise) => { - if(err === 'WIDGET_CLOSED' || err === 'ACTION_CANCELLED') { - return; - } - - logger.error(`Unhandled promise rejection from ${promise}, reason: ${err}`); - }); - net.createServer(socket => { logger.info('Socket opened'); diff --git a/src/main.ts b/src/main.ts index 026c40293..479d572d8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,10 @@ import { runGameServer } from './game-server'; import { runWebServer } from './web-server'; import 'source-map-support/register'; +import { initErrorHandling } from '@server/error-handling'; // import { dumpItems } from '@server/data-dump'; +initErrorHandling(); runGameServer(); runWebServer(); // dumpItems(); diff --git a/src/net/data-parser/client-login-parser.ts b/src/net/data-parser/client-login-parser.ts index 4d77281cb..440b2198c 100644 --- a/src/net/data-parser/client-login-parser.ts +++ b/src/net/data-parser/client-login-parser.ts @@ -34,25 +34,25 @@ export class ClientLoginParser extends DataParser { public parse(buffer?: RsBuffer): void { if(!buffer) { - throw ('No data supplied for login'); + throw new Error('No data supplied for login'); } const loginType = buffer.readUnsignedByte(); if(loginType !== 16 && loginType !== 18) { - throw ('Invalid login type ' + loginType); + throw new Error('Invalid login type ' + loginType); } let loginEncryptedSize = buffer.readUnsignedByte() - (36 + 1 + 1 + 2); if(loginEncryptedSize <= 0) { - throw ('Invalid login packet length ' + loginEncryptedSize); + throw new Error('Invalid login packet length ' + loginEncryptedSize); } const gameVersion = buffer.readIntBE(); if(gameVersion !== 435) { - throw ('Invalid game version ' + gameVersion); + throw new Error('Invalid game version ' + gameVersion); } const isLowDetail: boolean = buffer.readByte() === 1; @@ -72,7 +72,7 @@ export class ClientLoginParser extends DataParser { const blockId = decrypted.readByte(); if(blockId !== 10) { - throw ('Invalid block id ' + blockId); + throw new Error('Invalid block id ' + blockId); } const clientKey1 = decrypted.readIntBE(); @@ -80,7 +80,7 @@ export class ClientLoginParser extends DataParser { const incomingServerKey = decrypted.readLongBE(); if(this.clientConnection.serverKey !== incomingServerKey) { - throw (`Server key mismatch - ${this.clientConnection.serverKey} != ${incomingServerKey}`); + throw new Error(`Server key mismatch - ${this.clientConnection.serverKey} != ${incomingServerKey}`); } const clientUuid = decrypted.readIntBE(); diff --git a/src/net/data-parser/login-handshake-parser.ts b/src/net/data-parser/login-handshake-parser.ts index a6e0a417c..df554cc5a 100644 --- a/src/net/data-parser/login-handshake-parser.ts +++ b/src/net/data-parser/login-handshake-parser.ts @@ -8,7 +8,7 @@ export class LoginHandshakeParser extends DataParser { public parse(buffer: RsBuffer, packetId: number): void { if(!buffer) { - throw ('No data supplied for login handshake'); + throw new Error('No data supplied for login handshake'); } if(packetId === 14) { @@ -23,7 +23,7 @@ export class LoginHandshakeParser extends DataParser { this.clientConnection.serverKey = serverKey; } else { - throw 'Invalid login handshake packet id.'; + throw new Error('Invalid login handshake packet id.'); } } } diff --git a/src/net/data-parser/update-server-parser.ts b/src/net/data-parser/update-server-parser.ts index d939b2195..cbd8250ef 100644 --- a/src/net/data-parser/update-server-parser.ts +++ b/src/net/data-parser/update-server-parser.ts @@ -53,7 +53,7 @@ export class UpdateServerParser extends DataParser { } if(!cacheFile || cacheFile.getBuffer().length === 0) { - throw `Cache file not found; file(${file}) with index(${index})`; + throw new Error(`Cache file not found; file(${file}) with index(${index})`); } const cacheFileBuffer = cacheFile.getBuffer(); diff --git a/src/net/data-parser/version-handshake-parser.ts b/src/net/data-parser/version-handshake-parser.ts index c428d0109..78843875d 100644 --- a/src/net/data-parser/version-handshake-parser.ts +++ b/src/net/data-parser/version-handshake-parser.ts @@ -8,7 +8,7 @@ export class VersionHandshakeParser extends DataParser { public parse(buffer: RsBuffer, packetId: number): void { if(!buffer) { - throw ('No data supplied for version handshake'); + throw new Error('No data supplied for version handshake'); } if(packetId === 15) { @@ -18,7 +18,7 @@ export class VersionHandshakeParser extends DataParser { outputBuffer.writeByte(gameVersion === 435 ? 0 : 6); this.clientConnection.socket.write(outputBuffer.getData()); } else { - throw 'Invalid version handshake packet id.'; + throw new Error('Invalid version handshake packet id.'); } } } diff --git a/src/plugins/commands/give-item-command.ts b/src/plugins/commands/give-item-command.ts index c4a55a205..638bfee17 100644 --- a/src/plugins/commands/give-item-command.ts +++ b/src/plugins/commands/give-item-command.ts @@ -16,12 +16,12 @@ const action: commandAction = (details) => { let amount: number = args.amount as number; if(amount > 2000000000) { - throw `Unable to give more than 2,000,000,000.`; + throw new Error(`Unable to give more than 2,000,000,000.`); } const itemDefinition = gameCache.itemDefinitions.get(itemId); if(!itemDefinition) { - throw `Item ID ${itemId} not found!`; + throw new Error(`Item ID ${itemId} not found!`); } let actualAmount = 0; diff --git a/src/plugins/commands/new-dialogue-test-command.ts b/src/plugins/commands/new-dialogue-test-command.ts index 2d951d8d0..8444d1b09 100644 --- a/src/plugins/commands/new-dialogue-test-command.ts +++ b/src/plugins/commands/new-dialogue-test-command.ts @@ -32,7 +32,7 @@ const action: commandAction = (details) => { player => [ Emote.GENERIC, `See ya around.` ] ]).then(() => { // do something with dialogue result. - }).catch(() => {}); + }); }; export default new RunePlugin({ type: ActionType.COMMAND, commands: 'd', action }); diff --git a/src/plugins/npcs/lumbridge/lumbridge-farm-helpers-plugin.ts b/src/plugins/npcs/lumbridge/lumbridge-farm-helpers-plugin.ts index 02ee18f5f..962f695e7 100644 --- a/src/plugins/npcs/lumbridge/lumbridge-farm-helpers-plugin.ts +++ b/src/plugins/npcs/lumbridge/lumbridge-farm-helpers-plugin.ts @@ -47,10 +47,10 @@ const millieDialogue: npcAction = (details) => { player => [ Emote.HAPPY, `Great! Thanks for your help.` ] ], `I'm fine, thanks.`, [ - player => [ Emote.GENERIC, `Then I bring my wheat here?` ] + player => [ Emote.GENERIC, `I'm fine, thanks.` ] ] ] - ]).catch(() => {}); + ]); }; const gillieDialogue: npcAction = (details) => { @@ -82,7 +82,7 @@ const gillieDialogue: npcAction = (details) => { player => [ Emote.GENERIC, `I'm fine, thanks.` ], ] ] - ]).catch(() => {}); + ]); }; export default new RunePlugin([{ diff --git a/src/plugins/objects/ladders/ladder-plugin.ts b/src/plugins/objects/ladders/ladder-plugin.ts index 1565523c5..c19e42d6f 100644 --- a/src/plugins/objects/ladders/ladder-plugin.ts +++ b/src/plugins/objects/ladders/ladder-plugin.ts @@ -29,7 +29,7 @@ export const action: objectAction = (details) => { action({...details, option: `climb-${direction}`}); return; } - }).catch(error => console.error(error)); + }); return; } diff --git a/src/plugins/quests/cooks-assistant-quest.ts b/src/plugins/quests/cooks-assistant-quest.ts index d30118a09..e7172028d 100644 --- a/src/plugins/quests/cooks-assistant-quest.ts +++ b/src/plugins/quests/cooks-assistant-quest.ts @@ -160,7 +160,7 @@ const startQuestAction: npcAction = (details) => { `Go on your merry way!` ] ] ] - ]).catch(error => console.error(error)); + ]); }; function youStillNeed(quest: QuestProgress): DialogueTree { diff --git a/src/plugins/skills/skill-guide-plugin.ts b/src/plugins/skills/skill-guide-plugin.ts index e32aeedef..767d46785 100644 --- a/src/plugins/skills/skill-guide-plugin.ts +++ b/src/plugins/skills/skill-guide-plugin.ts @@ -32,7 +32,7 @@ function parseSkillGuides(): SkillGuide[] { const skillGuides = safeLoad(readFileSync('data/config/skill-guides.yaml', 'utf8'), { schema: JSON_SCHEMA }) as SkillGuide[]; if(!skillGuides || skillGuides.length === 0) { - throw 'Unable to read skill guides.'; + throw new Error('Unable to read skill guides.'); } logger.info(`${skillGuides.length} skill guides found.`); diff --git a/src/world/actor/dialogue.ts b/src/world/actor/dialogue.ts index d8f02e84d..a17c9bbea 100644 --- a/src/world/actor/dialogue.ts +++ b/src/world/actor/dialogue.ts @@ -5,6 +5,7 @@ import { gameCache } from '@server/game-server'; import { logger } from '@runejs/logger/dist/logger'; import _ from 'lodash'; import { wrapText } from '@server/util/strings'; +import { ActionsCancelledWarning, WidgetsClosedWarning } from '@server/error-handling'; export enum Emote { POMPOUS = 'POMPOUS', @@ -328,7 +329,7 @@ async function runParsedDialogue(player: Player, dialogueTree: ParsedDialogueTre for(let i = 0; i < dialogueTree.length; i++) { if(stopLoop) { - return Promise.reject('ACTION_CANCELLED'); + throw new ActionsCancelledWarning(); } const sub: Subscription[] = []; @@ -404,7 +405,8 @@ async function runParsedDialogue(player: Player, dialogueTree: ParsedDialogueTre const lines = textDialogueAction.lines; if(lines.length > 5) { - throw `Too many lines for text dialogue! Dialogue has ${lines.length} lines but the maximum is 5: ${JSON.stringify(lines)}`; + throw new Error(`Too many lines for text dialogue! Dialogue has ${lines.length} lines but ` + + `the maximum is 5: ${JSON.stringify(lines)}`); } widgetId = textWidgetIds[lines.length - 1]; @@ -461,7 +463,8 @@ async function runParsedDialogue(player: Player, dialogueTree: ParsedDialogueTre const lines = actorDialogueAction.lines; if(lines.length > 4) { - throw `Too many lines for actor dialogue! Dialogue has ${lines.length} lines but the maximum is 4: ${JSON.stringify(lines)}`; + throw new Error(`Too many lines for actor dialogue! Dialogue has ${lines.length} lines but ` + + `the maximum is 4: ${JSON.stringify(lines)}`); } const animation = actorDialogueAction.animation; @@ -498,7 +501,7 @@ async function runParsedDialogue(player: Player, dialogueTree: ParsedDialogueTre widgetId: widgetId, type: 'CHAT', closeOnWalk: true, - forceClosed: () => reject('WIDGET_CLOSED') + forceClosed: () => reject(new WidgetsClosedWarning()) }; } }).then(() => { @@ -506,6 +509,10 @@ async function runParsedDialogue(player: Player, dialogueTree: ParsedDialogueTre }).catch(error => { sub.forEach(s => s.unsubscribe()); stopLoop = true; + + if(!(error instanceof ActionsCancelledWarning) && !(error instanceof WidgetsClosedWarning)) { + throw error; + } }); } @@ -516,7 +523,7 @@ export async function dialogue(participants: (Player | NpcParticipant)[], dialog const player = participants.find(p => p instanceof Player) as Player; if(!player) { - return Promise.reject('Player instance not provided to dialogue action.'); + throw new Error('Player instance not provided to dialogue action.'); } let npcParticipants = participants.filter(p => !(p instanceof Player)) as NpcParticipant[]; diff --git a/src/world/actor/player/action/dialogue-action.ts b/src/world/actor/player/action/dialogue-action.ts index 00a9500f7..fb1304f51 100644 --- a/src/world/actor/player/action/dialogue-action.ts +++ b/src/world/actor/player/action/dialogue-action.ts @@ -1,6 +1,7 @@ import { Player } from '@server/world/actor/player/player'; import { gameCache } from '@server/game-server'; import { Npc } from '@server/world/actor/npc/npc'; +import { WidgetsClosedWarning } from '@server/error-handling'; export const dialogueWidgetIds = { PLAYER: [ 64, 65, 66, 67 ], @@ -85,11 +86,11 @@ export class DialogueAction { public async dialogue(options: DialogueOptions): Promise { if(options.lines.length < lineConstraints[options.type][0] || options.lines.length > lineConstraints[options.type][1]) { - throw 'Invalid line length.'; + throw new Error('Invalid line length.'); } if(options.type === 'NPC' && options.npc === undefined) { - throw 'NPC not supplied.'; + throw new Error('NPC not supplied.'); } this._action = null; @@ -138,7 +139,7 @@ export class DialogueAction { widgetId: widgetId, type: 'CHAT', closeOnWalk: true, - forceClosed: () => reject('WIDGET_CLOSED') + forceClosed: () => reject(new WidgetsClosedWarning()) }; const sub = this.p.dialogueInteractionEvent.subscribe(action => { diff --git a/src/world/actor/player/action/item-selection-action.ts b/src/world/actor/player/action/item-selection-action.ts index 9bb7bab06..91c04254c 100644 --- a/src/world/actor/player/action/item-selection-action.ts +++ b/src/world/actor/player/action/item-selection-action.ts @@ -58,7 +58,7 @@ export async function itemSelectionAction(player: Player, type: 'COOKING' | 'MAK widgetId = 309; } else { if(items.length > 5) { - throw `Too many items provided to the item selection action!`; + throw new Error(`Too many items provided to the item selection action!`); } widgetId = (301 + items.length); diff --git a/src/world/actor/player/player.ts b/src/world/actor/player/player.ts index 95928bc9d..3320500e5 100644 --- a/src/world/actor/player/player.ts +++ b/src/world/actor/player/player.ts @@ -536,13 +536,13 @@ export class Player extends Actor { return Promise.resolve(); } else { if(messages.length > 5) { - throw `Dialogues have a maximum of 5 lines!`; + throw new Error(`Dialogues have a maximum of 5 lines!`); } return dialogueAction(this, { type: 'TEXT', lines: messages }).then(async d => { d.close(); return Promise.resolve(); - }).catch(() => {}); + }); } } @@ -733,7 +733,6 @@ export class Player extends Actor { this.activeWidget = widget; } else { this.queuedWidgets.push(widget); - console.log(this.queuedWidgets); } } diff --git a/src/world/config/item-data.ts b/src/world/config/item-data.ts index 5bfcba959..68d7cb4db 100644 --- a/src/world/config/item-data.ts +++ b/src/world/config/item-data.ts @@ -123,7 +123,7 @@ export function parseItemData(itemDefinitions: Map): Map const itemDataList = safeLoad(readFileSync('data/config/item-data.yaml', 'utf8'), { schema: JSON_SCHEMA }) as ItemData[]; if(!itemDataList || itemDataList.length === 0) { - throw 'Unable to read item data.'; + throw new Error('Unable to read item data.'); } const itemDetailsMap: Map = new Map(); diff --git a/src/world/config/npc-spawn.ts b/src/world/config/npc-spawn.ts index c7acd8cd3..ba07dda36 100644 --- a/src/world/config/npc-spawn.ts +++ b/src/world/config/npc-spawn.ts @@ -19,7 +19,7 @@ export function parseNpcSpawns(): NpcSpawn[] { const npcSpawns = safeLoad(readFileSync('data/config/npc-spawns.yaml', 'utf8'), { schema: JSON_SCHEMA }) as NpcSpawn[]; if(!npcSpawns || npcSpawns.length === 0) { - throw 'Unable to read npc spawns.'; + throw new Error('Unable to read npc spawns.'); } logger.info(`${npcSpawns.length} npc spawns found.`); diff --git a/src/world/config/server-config.ts b/src/world/config/server-config.ts index 62ab2b13f..d03dd654a 100644 --- a/src/world/config/server-config.ts +++ b/src/world/config/server-config.ts @@ -19,7 +19,7 @@ export function parseServerConfig(useDefault?: boolean): ServerConfig { logger.warn('Server config not provided, using default...'); return parseServerConfig(true); } else { - throw 'Syntax Error'; + throw new Error('Syntax Error'); } } diff --git a/src/world/config/shops.ts b/src/world/config/shops.ts index 869107db1..d6509e179 100644 --- a/src/world/config/shops.ts +++ b/src/world/config/shops.ts @@ -30,7 +30,7 @@ export function parseShops(): Shop[] { const shops = safeLoad(readFileSync('data/config/shops.yaml', 'utf8'), { schema: JSON_SCHEMA }) as Shop[]; if(!shops || shops.length === 0) { - throw 'Unable to read shops.'; + throw new Error('Unable to read shops.'); } logger.info(`${shops.length} shops found.`);