node-gamedig/lib/QueryRunner.js
CosminPerRam 1ef09d470b
feat: breadth attempt order (#486)
* feat: breadth attempt order

* fix: remove stray console log from debugging
2024-01-18 23:11:03 +02:00

120 lines
3.4 KiB
JavaScript

import { lookup } from './game-resolver.js'
import { getProtocol } from './ProtocolResolver.js'
import GlobalUdpSocket from './GlobalUdpSocket.js'
const defaultOptions = {
socketTimeout: 2000,
attemptTimeout: 10000,
maxRetries: 1,
stripColors: true,
portCache: true,
noBreadthOrder: false,
ipFamily: 0
}
export default class QueryRunner {
constructor (runnerOpts = {}) {
this.udpSocket = new GlobalUdpSocket({
port: runnerOpts.listenUdpPort
})
this.portCache = {}
}
async run (userOptions) {
for (const key of Object.keys(userOptions)) {
const value = userOptions[key]
if (['port', 'ipFamily'].includes(key)) {
userOptions[key] = parseInt(value)
}
}
const {
port_query: gameQueryPort,
port_query_offset: gameQueryPortOffset,
...gameOptions
} = lookup(userOptions.type)
const attempts = []
const optionsCollection = {
...defaultOptions,
...gameOptions,
...userOptions
}
const addAttemptWithPort = port => {
attempts.push({
...optionsCollection,
port
})
}
let portOffsetArray = gameQueryPortOffset
if (!Array.isArray(portOffsetArray)) {
gameQueryPortOffset ? portOffsetArray = [gameQueryPortOffset] : portOffsetArray = [0]
}
const cachedPort = this.portCache[`${userOptions.address}:${userOptions.port}`]
if (cachedPort && optionsCollection.portCache) {
addAttemptWithPort(cachedPort)
}
if (userOptions.port) {
if (!userOptions.givenPortOnly) {
portOffsetArray.forEach((portOffset) => { addAttemptWithPort(userOptions.port + portOffset) })
if (userOptions.port === gameOptions.port && gameQueryPort) { addAttemptWithPort(gameQueryPort) }
}
attempts.push(optionsCollection)
} else if (gameQueryPort) {
addAttemptWithPort(gameQueryPort)
} else if (gameOptions.port) {
portOffsetArray.forEach((portOffset) => { addAttemptWithPort(gameOptions.port + portOffset) })
} else {
// Hopefully the request doesn't need a port. If it does, it'll fail when making the request.
attempts.push(optionsCollection)
}
const numRetries = userOptions.maxRetries || gameOptions.maxRetries || defaultOptions.maxRetries
const retries = Array.from({ length: numRetries }, (x, i) => i)
const attemptOrder = []
if (optionsCollection.noBreadthOrder) {
attempts.forEach(attempt => retries.forEach(retry => attemptOrder.push({ attempt, retry })))
} else {
retries.forEach(retry => attempts.forEach(attempt => attemptOrder.push({ attempt, retry })))
}
let attemptNum = 0
const errors = []
for (const { attempt, retry } of attemptOrder) {
attemptNum++
try {
const response = await this._attempt(attempt)
if (attempt.portCache) {
this.portCache[`${userOptions.address}:${userOptions.port}`] = attempt.port
}
return response
} catch (e) {
e.stack = 'Attempt #' + attemptNum + ' - Port=' + attempt.port + ' Retry=' + (retry) + ':\n' + e.stack
errors.push(e)
}
}
const err = new Error('Failed all ' + errors.length + ' attempts')
for (const e of errors) {
err.stack += '\n' + e.stack
}
throw err
}
async _attempt (options) {
const core = getProtocol(options.protocol)
core.options = options
core.udpSocket = this.udpSocket
return await core.runOnceSafe()
}
}