2019-05-13 12:00:56 +00:00
|
|
|
import url from 'url'
|
2019-06-09 10:41:03 +00:00
|
|
|
import matchRoute from 'my-way'
|
2019-05-13 12:00:56 +00:00
|
|
|
|
2019-06-20 03:33:53 +00:00
|
|
|
import fetchIcon from './fetch-icon'
|
2019-05-14 02:19:56 +00:00
|
|
|
import serveBadge from './serve-badge'
|
2019-06-20 03:33:53 +00:00
|
|
|
import serve404 from './serve-404'
|
2019-06-03 11:12:23 +00:00
|
|
|
import sentry from './sentry'
|
2019-05-13 12:00:56 +00:00
|
|
|
|
2019-05-14 01:36:25 +00:00
|
|
|
import { BadgenParams } from './types'
|
2019-05-13 12:00:56 +00:00
|
|
|
|
2019-05-26 04:38:30 +00:00
|
|
|
export type BadgenServeMeta = {
|
2019-05-26 05:09:19 +00:00
|
|
|
title: string
|
2019-05-26 04:38:30 +00:00
|
|
|
examples: { [url: string]: string }
|
2019-05-26 05:09:19 +00:00
|
|
|
help?: string
|
2019-05-26 04:38:30 +00:00
|
|
|
}
|
|
|
|
|
|
2019-05-13 12:00:56 +00:00
|
|
|
export type BadgenServeHandlerArgs = { [key: string]: string }
|
2019-05-31 02:07:18 +00:00
|
|
|
export type BadgenServeHandlerResult = Promise<BadgenParams | undefined>
|
|
|
|
|
export type BadgenServeHandler = (args: BadgenServeHandlerArgs) => BadgenServeHandlerResult
|
2019-05-13 12:00:56 +00:00
|
|
|
export type BadgenServeHandlers = { [key: string]: BadgenServeHandler }
|
|
|
|
|
|
2019-05-14 01:15:16 +00:00
|
|
|
export function badgenServe (handlers: BadgenServeHandlers): Function {
|
2019-05-13 12:00:56 +00:00
|
|
|
return async function httpHandler (req, res) {
|
|
|
|
|
const { pathname = '/', query } = url.parse(req.url, true)
|
|
|
|
|
|
|
|
|
|
// serve favicon
|
|
|
|
|
if (pathname === '/favicon.ico') {
|
|
|
|
|
return res.end()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Lookup handler
|
|
|
|
|
let matchedArgs
|
|
|
|
|
const matchedScheme = Object.keys(handlers).find(scheme => {
|
2019-05-15 13:13:07 +00:00
|
|
|
matchedArgs = matchRoute(scheme, decodeURI(pathname))
|
2019-05-13 12:00:56 +00:00
|
|
|
return matchedArgs !== null
|
|
|
|
|
})
|
|
|
|
|
|
2019-05-31 02:07:18 +00:00
|
|
|
const defaultLabel = pathname.split('/')[1]
|
2019-06-20 03:33:53 +00:00
|
|
|
const defaultParams = {
|
|
|
|
|
subject: defaultLabel,
|
|
|
|
|
status: 'unknown',
|
|
|
|
|
color: 'grey'
|
|
|
|
|
}
|
2019-06-01 01:50:59 +00:00
|
|
|
|
2019-05-13 12:00:56 +00:00
|
|
|
if (matchedScheme) {
|
2019-05-14 09:50:57 +00:00
|
|
|
try {
|
2019-06-20 03:33:53 +00:00
|
|
|
const paramsPromise = handlers[matchedScheme](matchedArgs)
|
|
|
|
|
|
|
|
|
|
let iconPromise
|
2019-06-22 16:30:49 +00:00
|
|
|
if (typeof query.icon === 'string') {
|
|
|
|
|
if (query.icon === '') {
|
|
|
|
|
iconPromise = Promise.resolve(defaultLabel)
|
|
|
|
|
} else if (query.icon.startsWith('https://')) {
|
|
|
|
|
iconPromise = fetchIcon(query.icon)
|
|
|
|
|
} else {
|
|
|
|
|
iconPromise = Promise.resolve(query.icon)
|
|
|
|
|
}
|
2019-05-31 02:07:18 +00:00
|
|
|
}
|
2019-06-01 01:50:59 +00:00
|
|
|
|
2019-06-22 16:30:49 +00:00
|
|
|
const [ icon, params = defaultParams ] = await Promise.all([
|
2019-06-23 02:14:15 +00:00
|
|
|
iconPromise.catch(e => undefined),
|
2019-06-22 16:30:49 +00:00
|
|
|
paramsPromise.catch(e => defaultParams)
|
2019-06-20 03:33:53 +00:00
|
|
|
])
|
|
|
|
|
|
2019-06-10 15:06:49 +00:00
|
|
|
params.subject = simpleDecode(params.subject)
|
|
|
|
|
params.status = simpleDecode(params.status)
|
2019-06-10 12:10:31 +00:00
|
|
|
|
2019-06-20 03:33:53 +00:00
|
|
|
query.icon = icon
|
|
|
|
|
|
2019-06-10 16:13:27 +00:00
|
|
|
if (query.style === undefined) {
|
2019-06-12 11:05:13 +00:00
|
|
|
const host = req.headers['x-forwarded-host'] || req.headers.host
|
|
|
|
|
if (host.startsWith('flat')) {
|
2019-06-10 16:13:27 +00:00
|
|
|
query.style = 'flat'
|
|
|
|
|
}
|
2019-06-01 01:50:59 +00:00
|
|
|
}
|
|
|
|
|
|
2019-05-14 09:50:57 +00:00
|
|
|
return serveBadge(req, res, { params, query })
|
|
|
|
|
} catch (error) {
|
2019-06-07 13:09:44 +00:00
|
|
|
if (error instanceof BadgenError) {
|
2019-06-16 05:08:48 +00:00
|
|
|
console.error(`BGE${error.code} "${error.status}" ${req.url}`)
|
2019-06-07 13:09:44 +00:00
|
|
|
return serveBadge(req, res, {
|
2019-06-07 13:17:30 +00:00
|
|
|
code: error.code,
|
2019-06-07 13:09:44 +00:00
|
|
|
sMaxAge: 5,
|
|
|
|
|
params: {
|
|
|
|
|
subject: defaultLabel,
|
|
|
|
|
status: error.status,
|
|
|
|
|
color: error.color
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-07 13:17:30 +00:00
|
|
|
// Handle timeout for `got` requests
|
|
|
|
|
if (error.code === 'ETIMEDOUT') {
|
2019-06-16 05:08:48 +00:00
|
|
|
console.error(`APIE504 ${req.url}`)
|
2019-05-26 07:54:24 +00:00
|
|
|
return serveBadge(req, res, {
|
2019-06-07 13:17:30 +00:00
|
|
|
code: 504,
|
2019-06-03 10:44:31 +00:00
|
|
|
sMaxAge: 5,
|
2019-05-26 07:54:24 +00:00
|
|
|
params: {
|
2019-05-31 02:07:18 +00:00
|
|
|
subject: defaultLabel,
|
2019-06-07 13:17:30 +00:00
|
|
|
status: 'timeout',
|
2019-05-26 07:54:24 +00:00
|
|
|
color: 'grey'
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-07 13:17:30 +00:00
|
|
|
// Handle requests errors from `got`
|
|
|
|
|
if (error.statusCode) {
|
|
|
|
|
const errorInfo = `${error.url} ${error.statusMessage}`
|
|
|
|
|
console.error(`APIE${error.statusCode} ${req.url} ${errorInfo}`)
|
2019-05-31 07:58:51 +00:00
|
|
|
return serveBadge(req, res, {
|
2019-06-16 05:08:48 +00:00
|
|
|
code: 502,
|
2019-06-01 16:21:34 +00:00
|
|
|
sMaxAge: 5,
|
2019-05-31 07:58:51 +00:00
|
|
|
params: {
|
|
|
|
|
subject: defaultLabel,
|
2019-06-07 13:17:30 +00:00
|
|
|
status: error.statusCode,
|
2019-06-01 04:43:58 +00:00
|
|
|
color: 'grey'
|
2019-05-31 07:58:51 +00:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-03 11:12:23 +00:00
|
|
|
sentry.configureScope((scope) => {
|
|
|
|
|
scope.setTag('path', req.url)
|
|
|
|
|
scope.setTag('service', defaultLabel)
|
|
|
|
|
})
|
|
|
|
|
sentry.captureException(error)
|
|
|
|
|
|
2019-06-16 05:08:48 +00:00
|
|
|
// uncatched error
|
|
|
|
|
console.error(`UCE ${req.url}`, error.message, error)
|
2019-05-14 09:50:57 +00:00
|
|
|
return serveBadge(req, res, {
|
|
|
|
|
code: 500,
|
2019-06-01 16:21:34 +00:00
|
|
|
sMaxAge: 5,
|
2019-05-14 09:50:57 +00:00
|
|
|
params: {
|
|
|
|
|
subject: 'badgen',
|
|
|
|
|
status: 'error',
|
2019-06-01 04:43:58 +00:00
|
|
|
color: 'grey'
|
2019-05-14 09:50:57 +00:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
2019-05-13 12:00:56 +00:00
|
|
|
} else {
|
|
|
|
|
return serve404(req, res)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-07 13:09:44 +00:00
|
|
|
|
|
|
|
|
export class BadgenError {
|
2019-06-07 13:17:30 +00:00
|
|
|
public status: string // error badge param: status (required)
|
|
|
|
|
public color: string // error badge param: color
|
|
|
|
|
public code: number // status code for response
|
2019-06-07 13:09:44 +00:00
|
|
|
|
|
|
|
|
constructor ({ status, color = 'grey', code = 500 }) {
|
|
|
|
|
this.status = status
|
|
|
|
|
this.color = color
|
|
|
|
|
this.code = code
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-10 15:06:49 +00:00
|
|
|
|
|
|
|
|
function simpleDecode (str: any): string {
|
|
|
|
|
return String(str).replace(/%2F/g, '/')
|
|
|
|
|
}
|