Changeset bdb78e4 in Klonkt for src/services/EmbedResolver.js
- Timestamp:
- 07/28/2026 08:59:00 AM (6 weeks ago)
- Branches:
- main
- Children:
- 2a76184
- Parents:
- b1512e0
- File:
-
- 1 edited
-
src/services/EmbedResolver.js (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/services/EmbedResolver.js
rb1512e0 rbdb78e4 59 59 if (!image && !title) return null; 60 60 return { image: image && /^https?:\/\//i.test(image) ? image : null, title: title || null, site: meta['og:site_name'] || null }; 61 } 62 63 /** 64 * Match a URL against the public oEmbed provider registry (oembed.com). 65 * 66 * Discovery through the page is the pure way, but it only works when the page 67 * hands you its <link rel=oembed>, and big platforms do not always do that: from 68 * a datacentre IP, YouTube serves a stripped page with no oEmbed link and no 69 * OpenGraph at all, while its oEmbed API answers perfectly. The registry closes 70 * that gap without us keeping a list of hosts: it is published and maintained by 71 * oembed.com, we only read it. 72 * 73 * Pure, so the pattern matching is testable without a network. 74 */ 75 export function matchProviderEndpoint(url, providers) { 76 if (!Array.isArray(providers)) return null; 77 let host = ''; 78 try { host = new URL(url).host.replace(/^www\./, ''); } catch { return null; } 79 const toRe = (scheme) => new RegExp('^' + String(scheme) 80 .replace(/[.+?^${}()|[\]\\]/g, '\\$&') 81 .replace(/\*/g, '.*') + '$', 'i'); 82 for (const p of providers) { 83 for (const ep of (p.endpoints || [])) { 84 const target = typeof ep.url === 'string' ? ep.url.replace('{format}', 'json') : null; 85 if (!target) continue; 86 for (const scheme of (ep.schemes || [])) { 87 if (toRe(scheme).test(url)) return target; 88 } 89 // No schemes listed: fall back to the provider's own host. 90 if (!ep.schemes || !ep.schemes.length) { 91 let phost = ''; 92 try { phost = new URL(p.provider_url).host.replace(/^www\./, ''); } catch { /* skip */ } 93 if (phost && (host === phost || host.endsWith('.' + phost))) return target; 94 } 95 } 96 } 97 return null; 98 } 99 100 /** Add the url + json format to an oEmbed endpoint. */ 101 export function oembedRequestUrl(endpoint, url) { 102 const sep = endpoint.includes('?') ? '&' : '?'; 103 return `${endpoint}${sep}format=json&url=${encodeURIComponent(url)}`; 61 104 } 62 105 … … 136 179 } 137 180 138 // 2. oEmbed, then OpenGraph. One page fetch serves both: oEmbed is the richer 139 // protocol, OpenGraph is the one most of the web actually ships. 181 // 2. The oEmbed registry: a cheap in-memory match, then one small API call. 182 // Tried before the page because it is far cheaper AND because the big 183 // platforms are exactly the ones that hide their tags from a server. 184 if (io.registry && io.getJSON) { 185 const providers = await io.registry().catch(() => null); 186 const endpoint = matchProviderEndpoint(url, providers); 187 if (endpoint) { 188 const o = await io.getJSON(oembedRequestUrl(endpoint, url)).catch(() => null); 189 const card = fromOEmbed(url, o); 190 if (card) return card; 191 } 192 } 193 194 // 3. oEmbed via the page, then OpenGraph. One page fetch serves both: oEmbed 195 // is the richer protocol, OpenGraph is the one most of the web ships. 140 196 // 141 197 // There is deliberately NO list of known providers here. A hardcoded list … … 247 303 * @param {object} deps - { safeFetch, detectProvider, actorInfo, fetchActor } 248 304 */ 305 // The provider registry, fetched once and kept for a day. It is a public list 306 // maintained by oembed.com, not by us; if it is unreachable we simply fall back 307 // to page discovery, so nothing breaks, it just gets less clever. 308 const REGISTRY_URL = 'https://oembed.com/providers.json'; 309 const REGISTRY_TTL = 24 * 60 * 60 * 1000; 310 let _registry = null; 311 let _registryAt = 0; 312 249 313 export function liveIO({ safeFetch, detectProvider, fetchActor, actorInfo }) { 250 314 return { 315 registry: async () => { 316 if (_registry && Date.now() - _registryAt < REGISTRY_TTL) return _registry; 317 const body = await safeJsonText(safeFetch, REGISTRY_URL, 'application/json'); 318 if (!body) return _registry; // keep a stale list over none 319 try { _registry = JSON.parse(body); _registryAt = Date.now(); } catch { /* keep the old one */ } 320 return _registry; 321 }, 251 322 provider: detectProvider ? (u) => { try { return detectProvider(u); } catch { return null; } } : null, 252 323 getAP: async (u) => { … … 273 344 } 274 345 275 export default { resolveEmbed, findOEmbedEndpoint, findOpenGraph, looksLikeAPObject, fromOEmbed, fromAPObject, liveIO };346 export default { resolveEmbed, findOEmbedEndpoint, findOpenGraph, matchProviderEndpoint, oembedRequestUrl, looksLikeAPObject, fromOEmbed, fromAPObject, liveIO };
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)