644 lines
25 KiB
HTML
644 lines
25 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>JSDoc: Source: core/base-service/base.js</title>
|
|
|
|
<script src="scripts/prettify/prettify.js"> </script>
|
|
<script src="scripts/prettify/lang-css.js"> </script>
|
|
<!--[if lt IE 9]>
|
|
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
<![endif]-->
|
|
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
|
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div id="main">
|
|
|
|
<h1 class="page-title">Source: core/base-service/base.js</h1>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<section>
|
|
<article>
|
|
<pre class="prettyprint source linenums"><code>/**
|
|
* @module
|
|
*/
|
|
|
|
// See available emoji at http://emoji.muan.co/
|
|
import emojic from 'emojic'
|
|
import Joi from 'joi'
|
|
import log from '../server/log.js'
|
|
import { AuthHelper } from './auth-helper.js'
|
|
import { MetricHelper, MetricNames } from './metric-helper.js'
|
|
import { assertValidCategory } from './categories.js'
|
|
import checkErrorResponse from './check-error-response.js'
|
|
import coalesceBadge from './coalesce-badge.js'
|
|
import {
|
|
NotFound,
|
|
InvalidResponse,
|
|
Inaccessible,
|
|
ImproperlyConfigured,
|
|
InvalidParameter,
|
|
Deprecated,
|
|
} from './errors.js'
|
|
import { fetch } from './got.js'
|
|
import { getEnum } from './openapi.js'
|
|
import {
|
|
makeFullUrl,
|
|
assertValidRoute,
|
|
prepareRoute,
|
|
namedParamsForMatch,
|
|
getQueryParamNames,
|
|
} from './route.js'
|
|
import { assertValidServiceDefinition } from './service-definitions.js'
|
|
import trace from './trace.js'
|
|
import validate from './validate.js'
|
|
|
|
const defaultBadgeDataSchema = Joi.object({
|
|
label: Joi.string(),
|
|
color: Joi.string(),
|
|
labelColor: Joi.string(),
|
|
namedLogo: Joi.string(),
|
|
}).required()
|
|
|
|
const optionalStringWhenNamedLogoPresent = Joi.alternatives().conditional(
|
|
'namedLogo',
|
|
{
|
|
is: Joi.string().required(),
|
|
then: Joi.string(),
|
|
},
|
|
)
|
|
|
|
const serviceDataSchema = Joi.object({
|
|
isError: Joi.boolean(),
|
|
label: Joi.string().allow(''),
|
|
// While a number of badges pass a number here, in the long run we may want
|
|
// `render()` to always return a string.
|
|
message: Joi.alternatives(Joi.string().allow(''), Joi.number()).required(),
|
|
color: Joi.string(),
|
|
link: Joi.array().items(Joi.string().uri()).single().max(2),
|
|
// Generally services should not use these options, which are provided to
|
|
// support the Endpoint badge.
|
|
labelColor: Joi.string(),
|
|
namedLogo: Joi.string(),
|
|
logoSvg: Joi.string(),
|
|
logoColor: optionalStringWhenNamedLogoPresent,
|
|
logoSize: optionalStringWhenNamedLogoPresent,
|
|
cacheSeconds: Joi.number().integer().min(0),
|
|
style: Joi.string(),
|
|
})
|
|
.oxor('namedLogo', 'logoSvg')
|
|
.required()
|
|
|
|
/**
|
|
* Abstract base class which all service classes inherit from.
|
|
* Concrete implementations of BaseService must implement the methods
|
|
* category(), route() and handle(namedParams, queryParams)
|
|
*/
|
|
class BaseService {
|
|
/**
|
|
* Name of the category to sort this badge into (eg. "build"). Used to sort
|
|
* the badges on the main shields.io website.
|
|
*
|
|
* @abstract
|
|
* @type {string}
|
|
*/
|
|
static get category() {
|
|
throw new Error(`Category not set for ${this.name}`)
|
|
}
|
|
|
|
static isDeprecated = false
|
|
|
|
/**
|
|
* Route to mount this service on
|
|
*
|
|
* @abstract
|
|
* @type {module:core/base-service/base~Route}
|
|
*/
|
|
static get route() {
|
|
throw new Error(`Route not defined for ${this.name}`)
|
|
}
|
|
|
|
/**
|
|
* Extract an array of allowed values from this service's route pattern
|
|
* for a given route parameter
|
|
*
|
|
* @param {string} param The name of a param in this service's route pattern
|
|
* @returns {string[]} Array of allowed values for this param
|
|
*/
|
|
static getEnum(param) {
|
|
if (!('pattern' in this.route)) {
|
|
throw new Error('getEnum() requires route to have a .pattern property')
|
|
}
|
|
const enumeration = getEnum(this.route.pattern, param)
|
|
if (!Array.isArray(enumeration)) {
|
|
throw new Error(
|
|
`Could not extract enum for param ${param} from pattern ${this.route.pattern}`,
|
|
)
|
|
}
|
|
return enumeration
|
|
}
|
|
|
|
/**
|
|
* Configuration for the authentication helper that prepares credentials
|
|
* for upstream requests.
|
|
*
|
|
* See also the config schema in `./server.js` and `doc/server-secrets.md`.
|
|
*
|
|
* To use the configured auth in the handler or fetch method, wrap the
|
|
* _request() input params in a call to one of:
|
|
* - this.authHelper.withBasicAuth()
|
|
* - this.authHelper.withBearerAuthHeader()
|
|
* - this.authHelper.withQueryStringAuth()
|
|
*
|
|
* For example:
|
|
* this._request(this.authHelper.withBasicAuth({ url, schema, options }))
|
|
*
|
|
* @abstract
|
|
* @type {module:core/base-service/base~Auth}
|
|
*/
|
|
static auth = undefined
|
|
|
|
/**
|
|
* An OpenAPI Paths Object describing this service's
|
|
* route or routes in OpenAPI format.
|
|
*
|
|
* @abstract
|
|
* @see https://swagger.io/specification/#paths-object
|
|
* @type {module:core/base-service/service-definitions~openApiSchema}
|
|
*/
|
|
static openApi = {}
|
|
|
|
static get _cacheLength() {
|
|
const cacheLengths = {
|
|
build: 30,
|
|
debug: 60,
|
|
|
|
'platform-support': 300,
|
|
size: 300,
|
|
version: 300,
|
|
|
|
chat: 1800,
|
|
downloads: 1800,
|
|
rating: 1800,
|
|
social: 1800,
|
|
|
|
license: 14400,
|
|
}
|
|
return cacheLengths[this.category]
|
|
}
|
|
|
|
/**
|
|
* Default data for the badge.
|
|
* These defaults are used if the value is neither included in the service data
|
|
* from the handler nor overridden by the user via query parameters.
|
|
*
|
|
* @type {module:core/base-service/base~DefaultBadgeData}
|
|
*/
|
|
static defaultBadgeData = {}
|
|
|
|
static render(props) {
|
|
throw new Error(`render() function not implemented for ${this.name}`)
|
|
}
|
|
|
|
static validateDefinition() {
|
|
assertValidCategory(this.category, `Category for ${this.name}`)
|
|
|
|
assertValidRoute(this.route, `Route for ${this.name}`)
|
|
|
|
Joi.assert(
|
|
this.defaultBadgeData,
|
|
defaultBadgeDataSchema,
|
|
`Default badge data for ${this.name}`,
|
|
)
|
|
|
|
// ensure openApi spec matches route
|
|
const preparedRoute = prepareRoute(this.route)
|
|
for (const [key, value] of Object.entries(this.openApi)) {
|
|
let example = key
|
|
for (const param of value.get.parameters) {
|
|
example = example.replace(`{${param.name}}`, param.example)
|
|
}
|
|
if (!example.match(preparedRoute.regex)) {
|
|
throw new Error(
|
|
`Inconsistent Open Api spec and Route found for service ${this.name}`,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
static getDefinition() {
|
|
const { category, name, isDeprecated, openApi } = this
|
|
const { base, format, pattern } = this.route
|
|
const queryParams = getQueryParamNames(this.route)
|
|
|
|
let route
|
|
if (pattern) {
|
|
route = { pattern: makeFullUrl(base, pattern), queryParams }
|
|
} else if (format) {
|
|
route = { format, queryParams }
|
|
} else {
|
|
route = undefined
|
|
}
|
|
|
|
const result = { category, name, isDeprecated, route, openApi }
|
|
|
|
assertValidServiceDefinition(result, `getDefinition() for ${this.name}`)
|
|
|
|
return result
|
|
}
|
|
|
|
constructor(
|
|
{ requestFetcher, authHelper, metricHelper },
|
|
{ handleInternalErrors },
|
|
) {
|
|
this._requestFetcher = requestFetcher
|
|
this.authHelper = authHelper
|
|
this._handleInternalErrors = handleInternalErrors
|
|
this._metricHelper = metricHelper
|
|
}
|
|
|
|
async _request({
|
|
url,
|
|
options = {},
|
|
httpErrors = {},
|
|
systemErrors = {},
|
|
logErrors = [429],
|
|
}) {
|
|
const logTrace = (...args) => trace.logTrace('fetch', ...args)
|
|
let logUrl = url
|
|
const logOptions = Object.assign({}, options)
|
|
if ('searchParams' in options && options.searchParams != null) {
|
|
const params = new URLSearchParams(
|
|
Object.fromEntries(
|
|
Object.entries(options.searchParams).filter(
|
|
([k, v]) => v !== undefined,
|
|
),
|
|
),
|
|
)
|
|
logUrl = `${url}?${params.toString()}`
|
|
delete logOptions.searchParams
|
|
}
|
|
logTrace(
|
|
emojic.bowAndArrow,
|
|
'Request',
|
|
`${logUrl}\n${JSON.stringify(logOptions, null, 2)}`,
|
|
)
|
|
const { res, buffer } = await this._requestFetcher(
|
|
url,
|
|
options,
|
|
systemErrors,
|
|
)
|
|
await this._meterResponse(res, buffer)
|
|
logTrace(emojic.dart, 'Response status code', res.statusCode)
|
|
return checkErrorResponse(httpErrors, logErrors)({ buffer, res })
|
|
}
|
|
|
|
static enabledMetrics = []
|
|
|
|
static isMetricEnabled(metricName) {
|
|
return this.enabledMetrics.includes(metricName)
|
|
}
|
|
|
|
async _meterResponse(res, buffer) {
|
|
if (
|
|
this._metricHelper &&
|
|
this.constructor.isMetricEnabled(MetricNames.SERVICE_RESPONSE_SIZE) &&
|
|
res.statusCode === 200
|
|
) {
|
|
this._metricHelper.noteServiceResponseSize(buffer.length)
|
|
}
|
|
}
|
|
|
|
static _validate(
|
|
data,
|
|
schema,
|
|
{
|
|
prettyErrorMessage = 'invalid response data',
|
|
includeKeys = false,
|
|
allowAndStripUnknownKeys = true,
|
|
} = {},
|
|
) {
|
|
return validate(
|
|
{
|
|
ErrorClass: InvalidResponse,
|
|
prettyErrorMessage,
|
|
includeKeys,
|
|
traceErrorMessage: 'Response did not match schema',
|
|
traceSuccessMessage: 'Response after validation',
|
|
allowAndStripUnknownKeys,
|
|
},
|
|
data,
|
|
schema,
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Asynchronous function to handle requests for this service. Take the route
|
|
* parameters (as defined in the `route` property), perform a request using
|
|
* `this._requestFetcher`, and return the badge data.
|
|
*
|
|
* @abstract
|
|
* @param {object} namedParams Params parsed from route pattern
|
|
* defined in this.route.pattern or this.route.capture
|
|
* @param {object} queryParams Params parsed from the query string
|
|
* @returns {module:core/base-service/base~Badge}
|
|
* badge Object validated against serviceDataSchema
|
|
*/
|
|
async handle(namedParams, queryParams) {
|
|
throw new Error(`Handler not implemented for ${this.constructor.name}`)
|
|
}
|
|
|
|
// Making this an instance method ensures debuggability.
|
|
// https://github.com/badges/shields/issues/3784
|
|
_validateServiceData(serviceData) {
|
|
Joi.assert(serviceData, serviceDataSchema)
|
|
}
|
|
|
|
_handleError(error) {
|
|
if (error instanceof NotFound || error instanceof InvalidParameter) {
|
|
trace.logTrace('outbound', emojic.noGoodWoman, 'Handled error', error)
|
|
return {
|
|
isError: true,
|
|
message: error.prettyMessage,
|
|
color: 'red',
|
|
}
|
|
} else if (
|
|
error instanceof ImproperlyConfigured ||
|
|
error instanceof InvalidResponse ||
|
|
error instanceof Inaccessible ||
|
|
error instanceof Deprecated
|
|
) {
|
|
trace.logTrace('outbound', emojic.noGoodWoman, 'Handled error', error)
|
|
const serviceData = {
|
|
isError: true,
|
|
message: error.prettyMessage,
|
|
color: 'lightgray',
|
|
}
|
|
if (error.cacheSeconds !== undefined) {
|
|
serviceData.cacheSeconds = error.cacheSeconds
|
|
}
|
|
return serviceData
|
|
} else if (this._handleInternalErrors) {
|
|
if (
|
|
!trace.logTrace(
|
|
'unhandledError',
|
|
emojic.boom,
|
|
'Unhandled internal error',
|
|
error,
|
|
)
|
|
) {
|
|
// This is where we end up if an unhandled exception is thrown in
|
|
// production. Send the error to Sentry and the logs.
|
|
log.error(error)
|
|
}
|
|
return {
|
|
isError: true,
|
|
label: 'shields',
|
|
message: 'internal error',
|
|
color: 'lightgray',
|
|
}
|
|
} else {
|
|
trace.logTrace(
|
|
'unhandledError',
|
|
emojic.boom,
|
|
'Unhandled internal error',
|
|
error,
|
|
)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
static async invoke(
|
|
context = {},
|
|
config = {},
|
|
namedParams = {},
|
|
queryParams = {},
|
|
) {
|
|
trace.logTrace('inbound', emojic.womanCook, 'Service class', this.name)
|
|
trace.logTrace('inbound', emojic.ticket, 'Named params', namedParams)
|
|
trace.logTrace('inbound', emojic.crayon, 'Query params', queryParams)
|
|
|
|
// Like the service instance, the auth helper could be reused for each request.
|
|
// However, moving its instantiation to `register()` makes `invoke()` harder
|
|
// to test.
|
|
const authHelper = this.auth ? new AuthHelper(this.auth, config) : undefined
|
|
|
|
const serviceInstance = new this({ ...context, authHelper }, config)
|
|
|
|
let serviceError
|
|
if (authHelper && !authHelper.isValid) {
|
|
const prettyMessage = authHelper.isRequired
|
|
? 'credentials have not been configured'
|
|
: 'credentials are misconfigured'
|
|
serviceError = new ImproperlyConfigured({ prettyMessage })
|
|
}
|
|
|
|
const { queryParamSchema } = this.route
|
|
let transformedQueryParams
|
|
if (!serviceError && queryParamSchema) {
|
|
try {
|
|
transformedQueryParams = validate(
|
|
{
|
|
ErrorClass: InvalidParameter,
|
|
prettyErrorMessage: 'invalid query parameter',
|
|
includeKeys: true,
|
|
traceErrorMessage: 'Query params did not match schema',
|
|
traceSuccessMessage: 'Query params after validation',
|
|
},
|
|
queryParams,
|
|
queryParamSchema,
|
|
)
|
|
trace.logTrace(
|
|
'inbound',
|
|
emojic.crayon,
|
|
'Query params after validation',
|
|
queryParams,
|
|
)
|
|
} catch (error) {
|
|
serviceError = error
|
|
}
|
|
} else {
|
|
transformedQueryParams = {}
|
|
}
|
|
|
|
let serviceData
|
|
if (!serviceError) {
|
|
try {
|
|
serviceData = await serviceInstance.handle(
|
|
namedParams,
|
|
transformedQueryParams,
|
|
)
|
|
serviceInstance._validateServiceData(serviceData)
|
|
} catch (error) {
|
|
serviceError = error
|
|
}
|
|
}
|
|
|
|
if (serviceError) {
|
|
serviceData = serviceInstance._handleError(serviceError)
|
|
}
|
|
|
|
trace.logTrace('outbound', emojic.shield, 'Service data', serviceData)
|
|
|
|
return serviceData
|
|
}
|
|
|
|
static register(
|
|
{
|
|
camp,
|
|
handleRequest,
|
|
githubApiProvider,
|
|
librariesIoApiProvider,
|
|
metricInstance,
|
|
},
|
|
serviceConfig,
|
|
) {
|
|
const { cacheHeaders: cacheHeaderConfig } = serviceConfig
|
|
const { regex, captureNames } = prepareRoute(this.route)
|
|
const queryParams = getQueryParamNames(this.route)
|
|
|
|
const metricHelper = MetricHelper.create({
|
|
metricInstance,
|
|
ServiceClass: this,
|
|
})
|
|
|
|
camp.route(
|
|
regex,
|
|
handleRequest(cacheHeaderConfig, {
|
|
queryParams,
|
|
handler: async (queryParams, match, sendBadge) => {
|
|
const metricHandle = metricHelper.startRequest()
|
|
|
|
const namedParams = namedParamsForMatch(captureNames, match, this)
|
|
const serviceData = await this.invoke(
|
|
{
|
|
requestFetcher: fetch,
|
|
githubApiProvider,
|
|
librariesIoApiProvider,
|
|
metricHelper,
|
|
},
|
|
serviceConfig,
|
|
namedParams,
|
|
queryParams,
|
|
)
|
|
|
|
const badgeData = coalesceBadge(
|
|
queryParams,
|
|
serviceData,
|
|
this.defaultBadgeData,
|
|
this,
|
|
)
|
|
// The final capture group is the extension.
|
|
const format = (match.slice(-1)[0] || '.svg').replace(/^\./, '')
|
|
sendBadge(format, badgeData)
|
|
|
|
metricHandle.noteResponseSent()
|
|
},
|
|
cacheLength: this._cacheLength,
|
|
}),
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Default badge properties, validated against defaultBadgeDataSchema
|
|
*
|
|
* @typedef {object} DefaultBadgeData
|
|
* @property {string} label (Optional)
|
|
* @property {string} color (Optional)
|
|
* @property {string} labelColor (Optional)
|
|
* @property {string} namedLogo (Optional)
|
|
*/
|
|
|
|
/**
|
|
* Badge Object, validated against serviceDataSchema
|
|
*
|
|
* @typedef {object} Badge
|
|
* @property {boolean} isError (Optional)
|
|
* @property {string} label (Optional)
|
|
* @property {(string|number)} message
|
|
* @property {string} color (Optional)
|
|
* @property {string[]} link (Optional)
|
|
*/
|
|
|
|
/**
|
|
* @typedef {object} Route
|
|
* @property {string} base
|
|
* (Optional) The base path of the routes for this service.
|
|
* This is used as a prefix.
|
|
* @property {string} pattern
|
|
* A path-to-regexp pattern defining the route pattern and param names
|
|
* See {@link https://www.npmjs.com/package/path-to-regexp}
|
|
* @property {RegExp} format
|
|
* Deprecated: Regular expression to use for routes for this service's badges
|
|
* Use `pattern` instead
|
|
* @property {string[]} capture
|
|
* Deprecated: Array of names for the capture groups in the regular
|
|
* expression. The handler will be passed an object containing
|
|
* the matches.
|
|
* Use `pattern` instead
|
|
* @property {Joi.object} queryParamSchema
|
|
* (Optional) A Joi schema (`Joi.object({ ... }).required()`)
|
|
* for the query param object. If you know a parameter
|
|
* will never receive a numeric string, you can use
|
|
* `Joi.string()`. Because of quirks in Scoutcamp and Joi,
|
|
* alphanumeric strings should be declared using
|
|
* `Joi.alternatives().try(Joi.string(), Joi.number())`,
|
|
* otherwise a value like `?success_color=999` will fail.
|
|
* A parameter requiring a numeric string can use
|
|
* `Joi.number()`. A parameter that receives only non-numeric
|
|
* strings can use `Joi.string()`. A parameter that never
|
|
* receives numeric can use `Joi.string()`. A boolean
|
|
* parameter should use `Joi.equal('')` and will receive an
|
|
* empty string on e.g. `?compact_message` and undefined
|
|
* when the parameter is absent. In the OpenApi definitions,
|
|
* this type of param should be documented as
|
|
* queryParam({
|
|
* name: 'compact_message', schema: { type: 'boolean' }, example: null
|
|
* })
|
|
*/
|
|
|
|
/**
|
|
* @typedef {object} Auth
|
|
* @property {string} userKey
|
|
* (Optional) The key from `privateConfig` to use as the username.
|
|
* @property {string} passKey
|
|
* (Optional) The key from `privateConfig` to use as the password.
|
|
* If auth is configured, either `userKey` or `passKey` is required.
|
|
* @property {string} isRequired
|
|
* (Optional) If `true`, the service will return `NotFound` unless the
|
|
* configured credentials are present.
|
|
*/
|
|
|
|
export default BaseService
|
|
</code></pre>
|
|
</article>
|
|
</section>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
<nav>
|
|
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-badge-maker.html">badge-maker</a></li><li><a href="module-badge-maker_lib_xml.html">badge-maker/lib/xml</a></li><li><a href="module-core_base-service_base.html">core/base-service/base</a></li><li><a href="module-core_base-service_base-graphql.html">core/base-service/base-graphql</a></li><li><a href="module-core_base-service_base-json.html">core/base-service/base-json</a></li><li><a href="module-core_base-service_base-svg-scraping.html">core/base-service/base-svg-scraping</a></li><li><a href="module-core_base-service_base-toml.html">core/base-service/base-toml</a></li><li><a href="module-core_base-service_base-xml.html">core/base-service/base-xml</a></li><li><a href="module-core_base-service_base-yaml.html">core/base-service/base-yaml</a></li><li><a href="module-core_base-service_errors.html">core/base-service/errors</a></li><li><a href="module-core_base-service_graphql.html">core/base-service/graphql</a></li><li><a href="module-core_base-service_openapi.html">core/base-service/openapi</a></li><li><a href="module-core_base-service_resource-cache.html">core/base-service/resource-cache</a></li><li><a href="module-core_base-service_service-definitions.html">core/base-service/service-definitions</a></li><li><a href="module-core_server_server.html">core/server/server</a></li><li><a href="module-core_service-test-runner_create-service-tester.html">core/service-test-runner/create-service-tester</a></li><li><a href="module-core_service-test-runner_icedfrisby-shields.html">core/service-test-runner/icedfrisby-shields</a></li><li><a href="module-core_service-test-runner_runner.html">core/service-test-runner/runner</a></li><li><a href="module-core_service-test-runner_service-tester.html">core/service-test-runner/service-tester</a></li><li><a href="module-core_service-test-runner_services-for-title.html">core/service-test-runner/services-for-title</a></li><li><a href="module-core_token-pooling_token-pool.html">core/token-pooling/token-pool</a></li><li><a href="module-services_build-status.html">services/build-status</a></li><li><a href="module-services_color-formatters.html">services/color-formatters</a></li><li><a href="module-services_contributor-count.html">services/contributor-count</a></li><li><a href="module-services_date.html">services/date</a></li><li><a href="module-services_downloads.html">services/downloads</a></li><li><a href="module-services_dynamic-common.html">services/dynamic-common</a></li><li><a href="module-services_dynamic_json-path.html">services/dynamic/json-path</a></li><li><a href="module-services_endpoint-common.html">services/endpoint-common</a></li><li><a href="module-services_licenses.html">services/licenses</a></li><li><a href="module-services_package-json-helpers.html">services/package-json-helpers</a></li><li><a href="module-services_php-version.html">services/php-version</a></li><li><a href="module-services_pipenv-helpers.html">services/pipenv-helpers</a></li><li><a href="module-services_route-builder.html">services/route-builder</a></li><li><a href="module-services_size.html">services/size</a></li><li><a href="module-services_steam_steam-base.html">services/steam/steam-base</a></li><li><a href="module-services_text-formatters.html">services/text-formatters</a></li><li><a href="module-services_validators.html">services/validators</a></li><li><a href="module-services_version.html">services/version</a></li><li><a href="module-services_website-status.html">services/website-status</a></li><li><a href="module-services_winget_version.html">services/winget/version</a></li></ul><h3>Classes</h3><ul><li><a href="BaseThunderstoreService.html">BaseThunderstoreService</a></li><li><a href="module-badge-maker_lib_xml-ElementList.html">ElementList</a></li><li><a href="module-badge-maker_lib_xml-XmlElement.html">XmlElement</a></li><li><a href="module-core_base-service_base-graphql-BaseGraphqlService.html">BaseGraphqlService</a></li><li><a href="module-core_base-service_base-json-BaseJsonService.html">BaseJsonService</a></li><li><a href="module-core_base-service_base-svg-scraping-BaseSvgScrapingService.html">BaseSvgScrapingService</a></li><li><a href="module-core_base-service_base-toml-BaseTomlService.html">BaseTomlService</a></li><li><a href="module-core_base-service_base-xml-BaseXmlService.html">BaseXmlService</a></li><li><a href="module-core_base-service_base-yaml-BaseYamlService.html">BaseYamlService</a></li><li><a href="module-core_base-service_base-BaseService.html">BaseService</a></li><li><a href="module-core_base-service_errors-Deprecated.html">Deprecated</a></li><li><a href="module-core_base-service_errors-ImproperlyConfigured.html">ImproperlyConfigured</a></li><li><a href="module-core_base-service_errors-Inaccessible.html">Inaccessible</a></li><li><a href="module-core_base-service_errors-InvalidParameter.html">InvalidParameter</a></li><li><a href="module-core_base-service_errors-InvalidResponse.html">InvalidResponse</a></li><li><a href="module-core_base-service_errors-NotFound.html">NotFound</a></li><li><a href="module-core_base-service_errors-ShieldsRuntimeError.html">ShieldsRuntimeError</a></li><li><a href="module-core_server_server-Server.html">Server</a></li><li><a href="module-core_service-test-runner_runner-Runner.html">Runner</a></li><li><a href="module-core_service-test-runner_service-tester-ServiceTester.html">ServiceTester</a></li><li><a href="module-core_token-pooling_token-pool-Token.html">Token</a></li><li><a href="module-core_token-pooling_token-pool-TokenPool.html">TokenPool</a></li><li><a href="module-services_route-builder.html">services/route-builder</a></li><li><a href="module-services_steam_steam-base-BaseSteamAPI.html">BaseSteamAPI</a></li></ul><h3>Tutorials</h3><ul><li><a href="tutorial-TUTORIAL.html">TUTORIAL</a></li><li><a href="tutorial-adding-new-config-values.html">adding-new-config-values</a></li><li><a href="tutorial-authentication.html">authentication</a></li><li><a href="tutorial-badge-urls.html">badge-urls</a></li><li><a href="tutorial-code-walkthrough.html">code-walkthrough</a></li><li><a href="tutorial-deprecating-badges.html">deprecating-badges</a></li><li><a href="tutorial-input-validation.html">input-validation</a></li><li><a href="tutorial-json-format.html">json-format</a></li><li><a href="tutorial-performance-testing.html">performance-testing</a></li><li><a href="tutorial-production-hosting.html">production-hosting</a></li><li><a href="tutorial-releases.html">releases</a></li><li><a href="tutorial-self-hosting.html">self-hosting</a></li><li><a href="tutorial-server-secrets.html">server-secrets</a></li><li><a href="tutorial-service-tests.html">service-tests</a></li><li><a href="tutorial-static-badges.html">static-badges</a></li></ul><h3>Global</h3><ul><li><a href="global.html#createNumRequestCounter">createNumRequestCounter</a></li><li><a href="global.html#fakeJwtToken">fakeJwtToken</a></li><li><a href="global.html#generateFakeConfig">generateFakeConfig</a></li><li><a href="global.html#getBadgeExampleCall">getBadgeExampleCall</a></li><li><a href="global.html#getServiceClassAuthOrigin">getServiceClassAuthOrigin</a></li><li><a href="global.html#isMetricWithPattern">isMetricWithPattern</a></li><li><a href="global.html#testAuth">testAuth</a></li></ul>
|
|
</nav>
|
|
|
|
<br class="clear">
|
|
|
|
<footer>
|
|
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Fri Mar 14 2025 19:47:18 GMT+0000 (Coordinated Universal Time)
|
|
</footer>
|
|
|
|
<script> prettyPrint(); </script>
|
|
<script src="scripts/linenumber.js"> </script>
|
|
</body>
|
|
</html>
|