n8n-nodes-link-preview
Version:
n8n node for getting link previews with HTML sanitization and rich text support
156 lines (155 loc) • 6.06 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LinkPreview = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const link_preview_js_1 = require("link-preview-js");
const dompurify_1 = __importDefault(require("dompurify"));
const jsdom_1 = require("jsdom");
// Initialize DOMPurify
const window = new jsdom_1.JSDOM('').window;
const DOMPurify = (0, dompurify_1.default)(window);
// Normalize URL by adding protocol if missing and handling special characters
function normalizeURL(url) {
try {
// Add https:// if no protocol is specified
if (!url.startsWith('http://') && !url.startsWith('https://')) {
url = 'https://' + url;
}
// Create URL object to normalize the URL
const urlObj = new URL(url);
// Remove trailing slash if present
if (urlObj.pathname === '/' && urlObj.search === '' && urlObj.hash === '') {
urlObj.pathname = '';
}
return urlObj.toString();
}
catch {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid URL format');
}
}
// Sanitize HTML content while preserving safe tags
function sanitizeHTML(str) {
if (!str)
return '';
// Configure DOMPurify to allow specific HTML tags and attributes
const config = {
ALLOWED_TAGS: [
'a', 'b', 'blockquote', 'code', 'em', 'i', 'li', 'ol', 'strong', 'ul',
'p', 'br', 'div', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
],
ALLOWED_ATTR: [
'href', 'target', 'rel', 'class', 'id', 'title', 'alt'
],
ALLOW_DATA_ATTR: false,
ALLOW_ARIA_ATTR: true,
ALLOW_UNKNOWN_PROTOCOLS: false,
FORBID_TAGS: ['script', 'style', 'iframe', 'object', 'embed'],
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onmouseout', 'onkeydown', 'onkeyup', 'onkeypress'],
};
return DOMPurify.sanitize(str, config);
}
// Sanitize array of HTML strings
function sanitizeHTMLArray(arr) {
if (!arr || !Array.isArray(arr))
return [];
return arr.map(str => sanitizeHTML(str));
}
class LinkPreview {
constructor() {
this.description = {
displayName: 'Link Preview',
name: 'linkPreview',
icon: 'file:icons/link.svg',
group: ['transform'],
version: 1,
description: 'Get preview information from a URL',
defaults: {
name: 'Link Preview',
},
inputs: ['main'],
outputs: ['main'],
properties: [
{
displayName: 'URL',
name: 'url',
type: 'string',
default: '',
required: true,
description: 'The URL to get preview information from',
noDataExpression: true,
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'Timeout (Seconds)',
name: 'timeout',
type: 'number',
default: 10,
description: 'Maximum time to wait for the preview in seconds',
},
{
displayName: 'Follow Redirects',
name: 'followRedirects',
type: 'boolean',
default: true,
description: 'Whether to follow redirects when fetching the preview',
},
],
},
],
};
}
async execute() {
const items = this.getInputData();
const returnData = [];
for (let i = 0; i < items.length; i++) {
const url = this.getNodeParameter('url', i);
const options = this.getNodeParameter('options', i);
try {
// Normalize and validate URL
const normalizedUrl = normalizeURL.call(this, url);
// Configure link preview options
const previewOptions = {
timeout: (options.timeout || 10) * 1000, // Convert to milliseconds
followRedirects: options.followRedirects !== false ? 'follow' : 'error',
};
const preview = await (0, link_preview_js_1.getLinkPreview)(normalizedUrl, previewOptions);
const sanitizedPreview = {
url: preview.url,
title: sanitizeHTML(preview.title),
description: sanitizeHTML(preview.description),
mediaType: preview.mediaType,
contentType: preview.contentType,
favicons: sanitizeHTMLArray(preview.favicons),
images: sanitizeHTMLArray(preview.images),
videos: sanitizeHTMLArray(preview.videos),
};
returnData.push({
json: sanitizedPreview,
});
}
catch {
if (this.continueOnFail()) {
returnData.push({
json: {
error: 'Unknown error occurred',
url,
},
});
continue;
}
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Unknown error occurred');
}
}
return [returnData];
}
}
exports.LinkPreview = LinkPreview;