OverviewFilesCommitsIssuesDocsReleasesPeers

holepunchto-deps / @holepunchto / keet-link-preview / oembed.js

62 lines · javascript ref:
import { userAgent } from 'fast-meta-tags'
import timeoutSignal from 'timeout-abort-signal'
import providers from './oembed.json' with { type: 'json' }
import { matchDomain } from './utils.js'

export async function fetchOembed(url, opt) {
  const oembedUrl = getOembedUrl(url)
  if (!oembedUrl) return null

  const t = timeoutSignal(opt?.signal, opt?.timeout)
  try {
    const res = await fetch(oembedUrl, {
      signal: t.signal,
      headers: { 'User-Agent': opt?.userAgent || userAgent }
    })
    if (!res.ok) return null

    const data = await res.json()
    const description = data.description || null
    return {
      title: data.title || null,
      name: data.provider_name || null,
      description: description && description.length > 500 ? description.slice(0, 500) : description,
      image: data.thumbnail_url || null
    }
  } catch {
    return null
  } finally {
    t.destroy()
  }
}

function getOembedUrl(url) {
  const endpoint = findEndpoint(url)
  if (!endpoint) return null
  const base = endpoint.replace('{format}', 'json')
  return `${base}?url=${encodeURIComponent(url)}`
}

function findEndpoint(url) {
  for (const provider of providers) {
    for (const endpoint of provider.endpoints) {
      if (endpoint.schemes) {
        for (const scheme of endpoint.schemes) {
          if (match(url, scheme)) return endpoint.url
        }
      } else if (matchDomain(url, provider.url)) {
        return endpoint.url
      }
    }
  }
  return null
}

export function match(url, scheme) {
  const pattern = scheme
    .replace(/[.+^${}()|[\]\\]/g, '\\$&')
    .replace(/\*/g, '[^.]*')
  const regex = new RegExp(`^${pattern}$`)
  return regex.test(url)
}