fast-speedtest-lm
Version:
> 以 fast-speedtest-api 更改
212 lines (191 loc) • 5.86 kB
JavaScript
const https = require('https')
const http = require('http')
const Timer = require('./Timer')
const ApiError = require('./ApiError')
const DEFAULT_SPEEDTEST_TIMEOUT = 5000 // ms
const DEFAULT_URL_COUNT = 5
const DEFAULT_BUFFER_SIZE = 8
const MAX_CHECK_INTERVAL = 200 // ms
class Api {
/**
* Create an Api object
*
* @param {object} options {token<string>, [verbose<boolean>, timeout<number>,
* https<boolean>, urlCount<number>, bufferSize<number>, unit<function>]}
*/
constructor(options) {
if (!options) {
throw new Error('You must define options in Api constructor')
}
if (!options.token) {
throw new Error('You must define app token')
}
if (options.unit && typeof options.unit !== 'function') {
throw new Error('Invalid unit')
}
this.token = options.token
this.verbose = options.verbose || false
this.timeout = options.timeout || DEFAULT_SPEEDTEST_TIMEOUT
this.https = options.https == null ? true : Boolean(options.https)
this.urlCount = options.urlCount || DEFAULT_URL_COUNT
this.bufferSize = options.bufferSize || DEFAULT_BUFFER_SIZE
this.unit = options.unit || Api.UNITS.Bps
}
/**
* Compute average from array of number
*
* @static
* @param {Array} arr array of number or null
* @return {number} The average
*/
static average(arr) {
// remove nulls from list
const arrWithoutNulls = arr.filter((e) => e)
if (arrWithoutNulls.length === 0) {
return 0
}
return arrWithoutNulls.reduce((a, b) => a + b) / arrWithoutNulls.length
}
/**
* Get data from the specified URL
*
* @async
* @param {string} url The URL to download from
* @return {Promise} The request and response from the URL
*/
get(url) {
return new Promise((resolve, reject) => {
const request = (this.https ? https : http)
.get(url, (response) => {
if (response.headers['content-type'].includes('json')) {
response.setEncoding('utf8')
let rawData = ''
response.on('data', (chunk) => {
rawData += chunk
})
response.on('end', () => {
const parsedData = JSON.parse(rawData)
response.data = parsedData
resolve({
response,
request,
})
})
} else {
resolve({
response,
request,
})
}
})
.on('error', (e) => {
reject(e)
})
})
}
/**
* Get videos to download url from Fast api
*
* @async
* @return {Array<string>} List of videos url
*/
async getTargets() {
try {
const targets = []
while (targets.length < this.urlCount) {
/* eslint-disable no-await-in-loop */
const { response } = await this.get(
`/speed/netflix/speedtest?https=${
this.https ? 'true' : 'false'
}&token=${this.token}&urlCount=${this.urlCount - targets.length}`
)
/* eslint-enable no-await-in-loop */
if (response.statusCode !== 200) {
if (response.statusCode === 403) {
throw new ApiError({ code: ApiError.CODES.BAD_TOKEN })
}
throw new ApiError({ code: ApiError.CODES.UNKNOWN })
}
targets.push(...response.data)
}
return targets.map((target) => target.url)
} catch (e) {
if (e.code === 'ENOTFOUND') {
if (this.https) {
throw new ApiError({ code: ApiError.CODES.UNREACHABLE_HTTPS_API })
} else {
throw new ApiError({ code: ApiError.CODES.UNREACHABLE_HTTP_API })
}
} else {
throw e
}
}
}
/**
* Resolves when timeout or when the first video finished downloading
*
* @returns {Promise<number>} Speed in selected unit (Default: Bps)
*/
async getSpeed() {
let targets = null
try {
targets = await this.getTargets()
} catch (e) {
console.error(e)
throw e
}
let bytes = 0
const requestList = []
const timer = new Timer(this.timeout, () => {
requestList.forEach((r) => r.abort())
})
targets.forEach(async (target) => {
const { response, request } = await this.get(target)
requestList.push(request)
response.on('data', (data) => {
bytes += data.length
})
response.on('end', () => {
// when first video is downloaded
timer.stop() // stop timer and execute timer callback
})
})
return new Promise((resolve) => {
let i = 0
const recents = new Array(this.bufferSize).fill(null) // list of most recent speeds
const interval = Math.min(
this.timeout / this.bufferSize,
MAX_CHECK_INTERVAL
) // ms
const refreshIntervalId = setInterval(() => {
i = (i + 1) % recents.length // loop through recents
recents[i] = bytes / (interval / 1000) // add most recent bytes/second
if (this.verbose) {
console.log(
`Current speed: ${this.unit(this.constructor.average(recents))} ${
this.unit.name
}`
)
}
bytes = 0 // reset bytes count
}, interval)
timer.addCallback(() => {
clearInterval(refreshIntervalId)
resolve(this.unit(this.constructor.average(recents)))
})
timer.start()
})
}
}
Api.UNITS = {
// rawSpeed is Bps
Bps: (rawSpeed) => rawSpeed,
KBps: (rawSpeed) => rawSpeed / 1000,
MBps: (rawSpeed) => rawSpeed / 1000000,
GBps: (rawSpeed) => rawSpeed / 1000000000,
bps: (rawSpeed) => rawSpeed * 8,
Kbps: (rawSpeed) => (rawSpeed * 8) / 1000,
Mbps: (rawSpeed) => (rawSpeed * 8) / 1000000,
Gbps: (rawSpeed) => (rawSpeed * 8) / 1000000000,
}
module.exports = Api