feat: closes #12656, only send required meta/link tags on /api calls

This commit is contained in:
Barış Soner Uşaklı
2024-06-26 11:55:24 -04:00
parent 85b329af25
commit 64875b3fae

View File

@@ -14,8 +14,10 @@ const relative_path = nconf.get('relative_path');
const upload_url = nconf.get('upload_url'); const upload_url = nconf.get('upload_url');
Tags.parse = async (req, data, meta, link) => { Tags.parse = async (req, data, meta, link) => {
const isAPI = req.res && req.res.locals && req.res.locals.isAPI;
// Meta tags // Meta tags
const defaultTags = [{ const defaultTags = isAPI ? [] : [{
name: 'viewport', name: 'viewport',
content: 'width=device-width, initial-scale=1.0', content: 'width=device-width, initial-scale=1.0',
}, { }, {
@@ -40,14 +42,14 @@ Tags.parse = async (req, data, meta, link) => {
content: Meta.config.themeColor || '#ffffff', content: Meta.config.themeColor || '#ffffff',
}]; }];
if (Meta.config.keywords) { if (Meta.config.keywords && !isAPI) {
defaultTags.push({ defaultTags.push({
name: 'keywords', name: 'keywords',
content: Meta.config.keywords, content: Meta.config.keywords,
}); });
} }
if (Meta.config['brand:logo']) { if (Meta.config['brand:logo'] && !isAPI) {
defaultTags.push({ defaultTags.push({
name: 'msapplication-square150x150logo', name: 'msapplication-square150x150logo',
content: Meta.config['brand:logo'], content: Meta.config['brand:logo'],
@@ -59,7 +61,7 @@ Tags.parse = async (req, data, meta, link) => {
const cacheBuster = `${Meta.config['cache-buster'] ? `?${Meta.config['cache-buster']}` : ''}`; const cacheBuster = `${Meta.config['cache-buster'] ? `?${Meta.config['cache-buster']}` : ''}`;
// Link Tags // Link Tags
const defaultLinks = [{ const defaultLinks = isAPI ? [] : [{
rel: 'icon', rel: 'icon',
type: 'image/x-icon', type: 'image/x-icon',
href: `${faviconPath}${cacheBuster}`, href: `${faviconPath}${cacheBuster}`,
@@ -69,7 +71,7 @@ Tags.parse = async (req, data, meta, link) => {
crossorigin: `use-credentials`, crossorigin: `use-credentials`,
}]; }];
if (plugins.hooks.hasListeners('filter:search.query')) { if (plugins.hooks.hasListeners('filter:search.query') && !isAPI) {
defaultLinks.push({ defaultLinks.push({
rel: 'search', rel: 'search',
type: 'application/opensearchdescription+xml', type: 'application/opensearchdescription+xml',
@@ -78,7 +80,59 @@ Tags.parse = async (req, data, meta, link) => {
}); });
} }
// Touch icons for mobile-devices if (!isAPI) {
addTouchIcons(defaultLinks);
}
const results = await utils.promiseParallel({
tags: plugins.hooks.fire('filter:meta.getMetaTags', { req: req, data: data, tags: defaultTags }),
links: plugins.hooks.fire('filter:meta.getLinkTags', { req: req, data: data, links: defaultLinks }),
});
meta = results.tags.tags.concat(meta || []).map((tag) => {
if (!tag || typeof tag.content !== 'string') {
winston.warn('Invalid meta tag. ', tag);
return tag;
}
if (!tag.noEscape) {
const attributes = Object.keys(tag);
attributes.forEach((attr) => {
tag[attr] = utils.escapeHTML(String(tag[attr]));
});
}
return tag;
});
await addSiteOGImage(meta);
addIfNotExists(meta, 'property', 'og:title', Meta.config.title || 'NodeBB');
const ogUrl = url + (req.originalUrl !== '/' ? stripRelativePath(req.originalUrl) : '');
addIfNotExists(meta, 'property', 'og:url', ogUrl);
addIfNotExists(meta, 'name', 'description', Meta.config.description);
addIfNotExists(meta, 'property', 'og:description', Meta.config.description);
link = results.links.links.concat(link || []);
if (isAPI) {
const whitelist = ['canonical', 'alternate', 'up'];
link = link.filter(link => whitelist.some(val => val === link.rel));
}
link = link.map((tag) => {
if (!tag.noEscape) {
const attributes = Object.keys(tag);
attributes.forEach((attr) => {
tag[attr] = utils.escapeHTML(String(tag[attr]));
});
}
return tag;
});
return { meta, link };
};
function addTouchIcons(defaultLinks) {
if (Meta.config['brand:touchIcon']) { if (Meta.config['brand:touchIcon']) {
defaultLinks.push({ defaultLinks.push({
rel: 'apple-touch-icon', rel: 'apple-touch-icon',
@@ -142,59 +196,16 @@ Tags.parse = async (req, data, meta, link) => {
href: `${relative_path}/assets/images/touch/512.png`, href: `${relative_path}/assets/images/touch/512.png`,
}); });
} }
}
const results = await utils.promiseParallel({
tags: plugins.hooks.fire('filter:meta.getMetaTags', { req: req, data: data, tags: defaultTags }),
links: plugins.hooks.fire('filter:meta.getLinkTags', { req: req, data: data, links: defaultLinks }),
});
meta = results.tags.tags.concat(meta || []).map((tag) => {
if (!tag || typeof tag.content !== 'string') {
winston.warn('Invalid meta tag. ', tag);
return tag;
}
if (!tag.noEscape) {
const attributes = Object.keys(tag);
attributes.forEach((attr) => {
tag[attr] = utils.escapeHTML(String(tag[attr]));
});
}
return tag;
});
await addSiteOGImage(meta);
addIfNotExists(meta, 'property', 'og:title', Meta.config.title || 'NodeBB');
const ogUrl = url + (req.originalUrl !== '/' ? stripRelativePath(req.originalUrl) : '');
addIfNotExists(meta, 'property', 'og:url', ogUrl);
addIfNotExists(meta, 'name', 'description', Meta.config.description);
addIfNotExists(meta, 'property', 'og:description', Meta.config.description);
link = results.links.links.concat(link || []).map((tag) => {
if (!tag.noEscape) {
const attributes = Object.keys(tag);
attributes.forEach((attr) => {
tag[attr] = utils.escapeHTML(String(tag[attr]));
});
}
return tag;
});
return { meta, link };
};
function addIfNotExists(meta, keyName, tagName, value) { function addIfNotExists(meta, keyName, tagName, value) {
const exists = meta.some(tag => tag[keyName] === tagName); const exists = meta.some(tag => tag[keyName] === tagName);
if (!exists && value) { if (!exists && value) {
const data = { meta.push({
content: utils.escapeHTML(String(value)), content: utils.escapeHTML(String(value)),
}; [keyName]: tagName,
data[keyName] = tagName; });
meta.push(data);
} }
} }