umami/lib/favicon.js

65 lines
2.3 KiB
JavaScript

/**
* ISC License
* Copyright 2022 github.com/sudoaugustin/favecon
* Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
import fetch from 'node-fetch';
import { parse } from 'node-html-parser';
import { TimeoutController } from 'timeout-abort-controller';
const filterLinks = links => {
const attrs = ['rel', 'href', 'sizes'];
const filterAttrs = link =>
attrs.reduce((total, attr) => ({ ...total, [attr]: link.getAttribute(attr) }), {});
return [...links]
.filter(link => link.getAttribute('href') && link.getAttribute('rel').includes('icon'))
.map(filterAttrs);
};
const formatValues = (url, icons) => {
const getOrigin = url => new URL(url).origin;
return icons.map(({ sizes, href, rel }) => ({
size: parseInt(sizes?.split('x')[0]) || undefined,
href: href[0] === '/' ? `${getOrigin(url)}${href}` : href,
rel,
}));
};
const fetchLinks = async url => {
// Time out the favicon request if it doesn't respond in a reasonable time.
const tc = new TimeoutController(3000);
let links = [];
try {
const html = await (await fetch(url, { signal: tc.signal })).text();
links = parse(html).querySelectorAll('head link');
} catch (e) {
// eslint-disable-next-line no-console
console.log(`Could not fetch favicon for url ${url}`, e);
} finally {
tc.clear();
}
return links;
};
const getIcons = async url => {
const links = await fetchLinks(url);
const icons = filterLinks(links);
return formatValues(url, icons);
};
export default async domain => {
const icons = await getIcons(`https://${domain}`);
if (icons.length && icons.length > 0) {
return icons[0]?.href;
}
return null;
};