UNPKG

n8n-nodes-google-pagespeed

Version:

n8n community node for Google PageSpeed Insights API with comprehensive performance, accessibility, and SEO analysis

238 lines 8.12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.normalizeUrl = normalizeUrl; exports.extractUrlFromInput = extractUrlFromInput; exports.isLikelyXmlUrl = isLikelyXmlUrl; exports.validateUrlContentType = validateUrlContentType; exports.batchNormalizeUrls = batchNormalizeUrls; exports.extractDomain = extractDomain; exports.isSameDomain = isSameDomain; exports.shortenUrlForDisplay = shortenUrlForDisplay; const config_1 = require("../config"); /** * Bulletproof URL normalization that guarantees a valid HTTPS URL * @param inputUrl - Raw URL input from user * @returns Normalized HTTPS URL * @throws Error with user-friendly message if URL cannot be fixed */ function normalizeUrl(inputUrl) { if (!inputUrl || typeof inputUrl !== 'string') { throw new Error(config_1.USER_FRIENDLY_ERROR_MESSAGES[config_1.ERROR_TYPES.INVALID_URL]); } // Clean the input let url = inputUrl.trim(); if (!url) { throw new Error('URL cannot be empty'); } // Remove any leading junk characters url = url.replace(/^[\/\s\.]+/, '').trim(); if (!url) { throw new Error('URL is empty after cleaning'); } // Force HTTPS protocol for security and consistency if (url.startsWith('http://')) { url = 'https://' + url.substring(7); } else if (!url.startsWith('https://')) { url = 'https://' + url; } // Validate the final URL try { const urlObj = new URL(url); // Must have a hostname with a TLD if (!urlObj.hostname || !urlObj.hostname.includes('.')) { throw new Error(`Invalid domain: ${urlObj.hostname}`); } // Remove common tracking parameters to normalize URLs urlObj.searchParams.delete('utm_source'); urlObj.searchParams.delete('utm_medium'); urlObj.searchParams.delete('utm_campaign'); urlObj.searchParams.delete('fbclid'); urlObj.searchParams.delete('gclid'); return urlObj.toString(); } catch (error) { throw new Error(`Cannot create valid URL from "${inputUrl}": ${error instanceof Error ? error.message : 'Invalid format'}`); } } /** * Extract URL from various input sources with intelligent fallback * @param context - n8n execution context * @param itemIndex - Item index in the execution * @returns Raw URL string from parameter or input data */ function extractUrlFromInput(context, itemIndex) { // Try parameter first let rawUrl = ''; try { rawUrl = context.getNodeParameter('url', itemIndex); } catch (error) { // Parameter failed, will try input data } // If parameter is empty or contains unresolved expressions, try input data if (!rawUrl || rawUrl.includes('{{') || rawUrl.includes('$json')) { try { const inputData = context.getInputData(); if (inputData[itemIndex] && inputData[itemIndex].json) { const jsonData = inputData[itemIndex].json; // Try multiple common field names for URLs const urlFields = [ 'URL To be Analized', // Keep original typo for compatibility 'URL To be Analyzed', // Correct spelling 'url', 'URL', 'website', 'link', 'domain', 'page', 'site', 'endpoint', ]; for (const field of urlFields) { if (jsonData[field] && typeof jsonData[field] === 'string') { rawUrl = jsonData[field]; break; } } } } catch (error) { // Input data failed, use whatever we have } } return rawUrl || ''; } /** * Check if URL is likely to return XML content instead of HTML * @param url - URL to check * @returns True if URL appears to be XML/API endpoint */ function isLikelyXmlUrl(url) { const urlLower = url.toLowerCase(); return config_1.PAGESPEED_CONFIG.XML_EXTENSIONS.some(ext => urlLower.includes(ext)) || config_1.PAGESPEED_CONFIG.XML_PATHS.some(path => urlLower.includes(path)); } /** * Validate that URL returns HTML content suitable for PageSpeed analysis * @param context - n8n execution context * @param url - URL to validate * @returns Content validation result */ async function validateUrlContentType(context, url) { try { // Quick check for obvious XML URLs if (isLikelyXmlUrl(url)) { return { isValid: false, contentType: 'likely-xml', error: 'URL appears to be XML/API endpoint. PageSpeed Insights requires HTML pages.' }; } // Perform HEAD request to check content type const options = { method: 'HEAD', url: url, timeout: config_1.PAGESPEED_CONFIG.HEAD_REQUEST_TIMEOUT, resolveWithFullResponse: true, headers: { 'User-Agent': 'Mozilla/5.0 (compatible; GooglePageSpeedBot/1.0)' } }; const response = await context.helpers.request(options); const contentType = (response.headers['content-type'] || '').toLowerCase(); const isHtml = contentType.includes(config_1.PAGESPEED_CONFIG.HTML_CONTENT_TYPE); return { isValid: isHtml, contentType: contentType, error: isHtml ? undefined : `Content-Type '${contentType}' is not HTML. Please provide a web page URL.` }; } catch (error) { // If validation fails due to network issues, allow it through // PageSpeed API will provide more specific error return { isValid: true, contentType: 'unknown', error: undefined }; } } /** * Batch process URLs with intelligent URL fixing * @param rawUrls - Array of raw URL strings * @returns Array of URL pairs with original and normalized versions */ function batchNormalizeUrls(rawUrls) { return rawUrls.map(rawUrl => { try { const normalized = normalizeUrl(rawUrl); return { original: rawUrl, normalized }; } catch (error) { return { original: rawUrl, normalized: rawUrl, error: error instanceof Error ? error.message : 'URL processing failed' }; } }); } /** * Extract domain from URL for categorization * @param url - URL to extract domain from * @returns Domain string */ function extractDomain(url) { try { const urlObj = new URL(url); return urlObj.hostname; } catch (error) { return 'unknown'; } } /** * Check if URL is on the same domain * @param url1 - First URL * @param url2 - Second URL * @returns True if both URLs are on the same domain */ function isSameDomain(url1, url2) { try { const domain1 = new URL(url1).hostname; const domain2 = new URL(url2).hostname; return domain1 === domain2; } catch (error) { return false; } } /** * Generate a short URL for display purposes * @param url - Full URL * @param maxLength - Maximum length for display * @returns Shortened URL string */ function shortenUrlForDisplay(url, maxLength = 50) { if (url.length <= maxLength) { return url; } try { const urlObj = new URL(url); const domain = urlObj.hostname; const path = urlObj.pathname; if (domain.length + 10 >= maxLength) { return domain.substring(0, maxLength - 3) + '...'; } const availableLength = maxLength - domain.length - 3; if (path.length > availableLength) { return domain + path.substring(0, availableLength) + '...'; } return domain + path; } catch (error) { return url.substring(0, maxLength - 3) + '...'; } } //# sourceMappingURL=urlUtils.js.map