// AI Visibility Audit – Main JavaScript (CORS-Free Version)
// Configuration
const CONFIG = {
googleApiKey: ‘AIzaSyAlXfpY2Y7k_1EU2oWSHT6wplFZYzrQ9wQ’,
web3formsKey: ‘0c6d9fe3-e589-4e41-9d18-276360df2ece’
};
// PageSpeed Insights API helper
class PageSpeedAPI {
constructor(apiKey) {
this.apiKey = apiKey;
this.apiUrl = ‘https://www.googleapis.com/pagespeedonline/v5/runPagespeed’;
}
async analyze(url) {
try {
const response = await fetch(
`${this.apiUrl}?url=${encodeURIComponent(url)}&key=${this.apiKey}&category=SEO&category=PERFORMANCE`
);
const data = await response.json();
if (data.lighthouseResult) {
return this.parseResults(data.lighthouseResult);
}
return null;
} catch (error) {
console.error(‘PageSpeed API error:’, error);
return null;
}
}
parseResults(lighthouse) {
const audits = lighthouse.audits;
const finalUrl = lighthouse.finalUrl;
const titleAudit = audits[‘document-title’];
const metaDescAudit = audits[‘meta-description’];
const structuredDataAudit = audits[‘structured-data’];
const structuredDataItems = structuredDataAudit?.details?.items || [];
return {
title: titleAudit?.displayValue || titleAudit?.title || null,
metaDescription: metaDescAudit?.displayValue || metaDescAudit?.description || null,
hasValidTitle: titleAudit?.score === 1,
hasValidMetaDesc: metaDescAudit?.score === 1,
structuredData: structuredDataItems,
hasStructuredData: structuredDataItems.length > 0,
structuredDataScore: structuredDataAudit?.score,
isMobileFriendly: audits[‘viewport’]?.score === 1,
isHTTPS: finalUrl?.startsWith(‘https://’),
hasValidHreflang: audits[‘hreflang’]?.score !== 0,
isIndexable: audits[‘is-crawlable’]?.score === 1 || audits[‘is-crawlable’]?.score === null,
hasValidCanonical: audits[‘canonical’]?.score !== 0,
performanceScore: Math.round((lighthouse.categories?.performance?.score || 0) * 100),
seoScore: Math.round((lighthouse.categories?.seo?.score || 0) * 100),
_debug: {
titleAudit: titleAudit,
metaDescAudit: metaDescAudit,
structuredDataAudit: structuredDataAudit
}
};
}
}
// Audit engine
class VisibilityAuditor {
constructor(url, businessName) {
this.url = url;
this.businessName = businessName;
this.results = {
seo: [],
local: [],
ai: [],
social: []
};
this.pageSpeed = new PageSpeedAPI(CONFIG.googleApiKey);
this.pageSpeedData = null;
}
async runAudit() {
this.pageSpeedData = await this.pageSpeed.analyze(this.url);
await Promise.all([
this.checkSEO(),
this.checkLocal(),
this.checkAI(),
this.checkSocial()
]);
return this.calculateScores();
}
async checkSEO() {
const checks = [];
if (this.pageSpeedData) {
console.log(‘PageSpeed Data:’, this.pageSpeedData._debug);
let html = null;
try {
console.log(‘Fetching HTML for backup checks…’);
const response = await fetch(`https://api.allorigins.win/raw?url=${encodeURIComponent(this.url)}`);
html = await response.text();
console.log(‘HTML fetched successfully, length:’, html.length);
} catch (error) {
console.error(‘Failed to fetch HTML for backup checks:’, error);
}
checks.push({
title: ‘Secure Connection (HTTPS)’,
passed: this.pageSpeedData.isHTTPS,
detail: this.pageSpeedData.isHTTPS
? ‘Site uses secure HTTPS protocol’
: ‘Site should use HTTPS for security and SEO’,
verified: true
});
const titleDetails = this.pageSpeedData._debug.titleAudit;
const titleText = titleDetails?.displayValue ||
titleDetails?.description ||
(titleDetails?.score === 1 ? ‘Title tag found’ : null);
checks.push({
title: ‘Title Tag Present & Optimized’,
passed: this.pageSpeedData.hasValidTitle,
detail: titleText || ‘Missing or invalid title tag – critical for SEO’,
verified: true
});
const metaDetails = this.pageSpeedData._debug.metaDescAudit;
const metaText = metaDetails?.displayValue ||
metaDetails?.description ||
(metaDetails?.score === 1 ? ‘Meta description found’ : null);
checks.push({
title: ‘Meta Description’,
passed: this.pageSpeedData.hasValidMetaDesc,
detail: metaText || ‘Missing meta description’,
verified: true
});
const schemaDetails = this.pageSpeedData._debug.structuredDataAudit;
let schemaCount = this.pageSpeedData.structuredData.length;
let hasStructuredData = this.pageSpeedData.hasStructuredData;
if (!hasStructuredData && html) {
console.log(‘Running backup schema check…’);
const jsonLdMatches = html.match(/
