Skip to content

Commit

Permalink
feat: add base64 URLs support
Browse files Browse the repository at this point in the history
  • Loading branch information
Kikobeats committed Nov 21, 2021
1 parent 7e664a4 commit 814b76c
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 22 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 100
max_line_length = 80
indent_brace_style = 1TBS
spaces_around_operators = true
quote_type = auto
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@
"cheerio": "~1.0.0-rc.10",
"compression": "~1.7.4",
"cors": "~2.8.5",
"data-uri-regex": "~0.1.4",
"data-uri-to-buffer": "~3.0.1",
"debug-logfmt": "~1.0.4",
"express": "~4.17.1",
"got": "~11.8.2",
Expand Down
33 changes: 24 additions & 9 deletions src/avatar/auto.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict'

const isAbsoluteUrl = require('is-absolute-url')
const dataUriRegex = require('data-uri-regex')
const reachableUrl = require('reachable-url')
const isEmail = require('is-email-like')
const pTimeout = require('p-timeout')
Expand All @@ -21,22 +22,36 @@ const is = input => {
return 'username'
}

const getAvatarUrl = async (fn, ...args) => {
const avatarUrl = await fn(...args)
if (typeof avatarUrl !== 'string' || !isAbsoluteUrl(avatarUrl)) {
throw new Error('Avatar URL is not valid.')
const getAvatarContent = async input => {
if (typeof input !== 'string') {
throw new Error(`Avatar \`${input}\` is not valid.`)
}
const { statusCode, url } = await reachableUrl(avatarUrl, gotOpts)
if (!isReachable({ statusCode })) throw new Error(`Avatar \`${url}\` returns \`${statusCode}\``)
return url

if (dataUriRegex().test(input)) {
return { type: 'buffer', data: input }
}

if (!isAbsoluteUrl(input)) {
throw new Error('Avatar as URL should be absolute.')
}

const { statusCode, url } = await reachableUrl(input, gotOpts)

if (!isReachable({ statusCode })) {
throw new Error(`Avatar \`${url}\` is unreachable (\`${statusCode}\`)`)
}

return { type: 'url', data: url }
}

const getAvatar = async (fn, ...args) => fn(...args).then(getAvatarContent)

module.exports = async input => {
const collection = get(providersBy, is(input))
const promises = collection.map(providerName =>
pTimeout(getAvatarUrl(get(providers, providerName), input), AVATAR_TIMEOUT)
pTimeout(getAvatar(get(providers, providerName), input), AVATAR_TIMEOUT)
)
return pAny(promises)
}

module.exports.getAvatarUrl = getAvatarUrl
module.exports.getAvatar = getAvatar
4 changes: 2 additions & 2 deletions src/avatar/provider.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use strict'

const { getAvatarUrl } = require('./auto')
const { getAvatar } = require('./auto')

module.exports = fn => async (...args) => getAvatarUrl(fn, ...args)
module.exports = fn => async (...args) => getAvatar(fn, ...args)
16 changes: 11 additions & 5 deletions src/avatar/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ const optimizeUrl = async (url, query) => {

const { statusCode, url: resourceUrl } = await reachableUrl(proxyUrl, gotOpts)
const optimizedUrl = isReachable({ statusCode }) ? resourceUrl : url
debug('optimizeUrl', { original: url, optimized: optimizedUrl })
return optimizedUrl
}

Expand All @@ -49,18 +48,25 @@ module.exports = fn => async (req, res) => {
const host = req.get('host')
const input = get(req, 'params.key')

const { value, reason, isRejected } = await pReflect(
let { value, reason, isRejected } = await pReflect(
pTimeout(fn(input, req, res), AVATAR_TIMEOUT)
)
const url = value || getFallbackUrl({ query, protocol, host })

if (isRejected) {
debug.error((reason.message || reason).trim())
}

if (value && value.type === 'url') {
value.data = await optimizeUrl(value.data, query)
}

if (value === undefined) {
value = { type: 'url', data: getFallbackUrl({ query, protocol, host }) }
}

return {
url: await optimizeUrl(url, query),
...value,
isJSON: !isNil(get(req, 'query.json')),
isError: isNil(url)
isError: isNil(value)
}
}
17 changes: 12 additions & 5 deletions src/send/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict'

const dataUriToBuffer = require('data-uri-to-buffer')
const { promisify } = require('util')
const { pickBy } = require('lodash')
const stream = require('stream')
Expand All @@ -10,15 +11,21 @@ const got = require('../util/got')

const { ALLOWED_REQ_HEADERS } = require('../constant')

const sendAvatar = ({ req, res, url, isError }) => {
const pickHeaders = headers =>
pickBy(headers, (value, key) => ALLOWED_REQ_HEADERS.includes(key))

const sendAvatar = ({ req, res, type, data, isError }) => {
if (isError) return res.send()
const headers = pickBy(req.headers, (value, key) => ALLOWED_REQ_HEADERS.includes(key))
return pipeline(got.stream(url, { headers }), res)
return type === 'buffer'
? res.send(dataUriToBuffer(data))
: pipeline(got.stream(data, { headers: pickHeaders(req.headers) }), res)
}

const send = ({ url, req, res, isJSON, isError }) => {
const send = ({ type, data, req, res, isJSON, isError }) => {
res.status(isError ? 404 : 200)
return isJSON ? res.json({ url }) : sendAvatar({ req, res, url, isError })
return isJSON
? res.json({ url: data })
: sendAvatar({ req, res, type, data, isError })
}

module.exports = send

1 comment on commit 814b76c

@vercel
Copy link

@vercel vercel bot commented on 814b76c Nov 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.