UNPKG

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
"use strict"; 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;