You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
81 lines
2.6 KiB
81 lines
2.6 KiB
import { TWebMetadata } from '@/types' |
|
import DataLoader from 'dataloader' |
|
|
|
class WebService { |
|
static instance: WebService |
|
|
|
private webMetadataDataLoader = new DataLoader<string, TWebMetadata>( |
|
async (urls) => { |
|
return await Promise.all( |
|
urls.map(async (url) => { |
|
// Check if we should use proxy server to avoid CORS issues |
|
const proxyServer = import.meta.env.VITE_PROXY_SERVER |
|
const isProxyUrl = url.includes('/sites/') |
|
|
|
// If proxy is configured and URL isn't already proxied, use proxy |
|
let fetchUrl = url |
|
if (proxyServer && !isProxyUrl) { |
|
fetchUrl = `${proxyServer}/sites/${encodeURIComponent(url)}` |
|
} |
|
|
|
try { |
|
|
|
// Add timeout and better error handling |
|
const controller = new AbortController() |
|
const timeoutId = setTimeout(() => controller.abort(), 5000) // 5 second timeout for proxy |
|
|
|
// Fetch with appropriate headers |
|
const res = await fetch(fetchUrl, { |
|
signal: controller.signal, |
|
mode: 'cors', |
|
credentials: 'omit', |
|
headers: { |
|
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' |
|
} |
|
}) |
|
|
|
clearTimeout(timeoutId) |
|
|
|
if (!res.ok) { |
|
return {} |
|
} |
|
|
|
const html = await res.text() |
|
const parser = new DOMParser() |
|
const doc = parser.parseFromString(html, 'text/html') |
|
|
|
const title = |
|
doc.querySelector('meta[property="og:title"]')?.getAttribute('content') || |
|
doc.querySelector('title')?.textContent |
|
const description = |
|
doc.querySelector('meta[property="og:description"]')?.getAttribute('content') || |
|
(doc.querySelector('meta[name="description"]') as HTMLMetaElement | null)?.content |
|
const image = (doc.querySelector('meta[property="og:image"]') as HTMLMetaElement | null) |
|
?.content |
|
|
|
return { title, description, image } |
|
} catch (error) { |
|
// Silent fail - return empty metadata on any error |
|
return {} |
|
} |
|
}) |
|
) |
|
}, |
|
{ maxBatchSize: 1, batchScheduleFn: (callback) => setTimeout(callback, 100) } |
|
) |
|
|
|
constructor() { |
|
if (!WebService.instance) { |
|
WebService.instance = this |
|
} |
|
return WebService.instance |
|
} |
|
|
|
async fetchWebMetadata(url: string) { |
|
return await this.webMetadataDataLoader.load(url) |
|
} |
|
} |
|
|
|
const instance = new WebService() |
|
|
|
export default instance
|
|
|