@ox-content/vite-plugin 2.75.0 → 2.76.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ //#region src/interop.ts
2
+ /**
3
+ * Interop helpers for loading ESM-only dependencies from the CommonJS build.
4
+ */
5
+ /**
6
+ * Unwrap the `default` export of an ESM-only dependency.
7
+ *
8
+ * The CommonJS build (`dist/index.cjs`) loads pure-ESM packages such as
9
+ * `rehype-parse` and `rehype-stringify` through `require(esm)`. The bundler's
10
+ * interop helper then double-wraps their default export: what should be the
11
+ * plugin function arrives as the module namespace `{ default: fn }` instead of
12
+ * `fn` itself. Passing that object to `unified().use()` throws
13
+ * "Expected usable value but received an empty preset".
14
+ *
15
+ * Calling `interopDefault` on a default import unwraps one extra level of
16
+ * `default` nesting, so the same code works whether the dependency was loaded
17
+ * as native ESM (`.mjs`) or through the CommonJS interop (`.cjs`). See #452.
18
+ */
19
+ function interopDefault(value) {
20
+ if (value !== null && typeof value === "object" && "default" in value) return value.default;
21
+ return value;
22
+ }
23
+ //#endregion
24
+ Object.defineProperty(exports, "interopDefault", {
25
+ enumerable: true,
26
+ get: function() {
27
+ return interopDefault;
28
+ }
29
+ });
30
+
31
+ //# sourceMappingURL=interop.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interop.cjs","names":[],"sources":["../src/interop.ts"],"sourcesContent":["/**\n * Interop helpers for loading ESM-only dependencies from the CommonJS build.\n */\n\n/**\n * Unwrap the `default` export of an ESM-only dependency.\n *\n * The CommonJS build (`dist/index.cjs`) loads pure-ESM packages such as\n * `rehype-parse` and `rehype-stringify` through `require(esm)`. The bundler's\n * interop helper then double-wraps their default export: what should be the\n * plugin function arrives as the module namespace `{ default: fn }` instead of\n * `fn` itself. Passing that object to `unified().use()` throws\n * \"Expected usable value but received an empty preset\".\n *\n * Calling `interopDefault` on a default import unwraps one extra level of\n * `default` nesting, so the same code works whether the dependency was loaded\n * as native ESM (`.mjs`) or through the CommonJS interop (`.cjs`). See #452.\n */\nexport function interopDefault<T>(value: T): T {\n if (value !== null && typeof value === \"object\" && \"default\" in value) {\n return (value as { default: T }).default;\n }\n return value;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,SAAgB,eAAkB,OAAa;CAC7C,IAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,aAAa,OAC9D,OAAQ,MAAyB;CAEnC,OAAO"}
@@ -0,0 +1,26 @@
1
+ //#region src/interop.ts
2
+ /**
3
+ * Interop helpers for loading ESM-only dependencies from the CommonJS build.
4
+ */
5
+ /**
6
+ * Unwrap the `default` export of an ESM-only dependency.
7
+ *
8
+ * The CommonJS build (`dist/index.cjs`) loads pure-ESM packages such as
9
+ * `rehype-parse` and `rehype-stringify` through `require(esm)`. The bundler's
10
+ * interop helper then double-wraps their default export: what should be the
11
+ * plugin function arrives as the module namespace `{ default: fn }` instead of
12
+ * `fn` itself. Passing that object to `unified().use()` throws
13
+ * "Expected usable value but received an empty preset".
14
+ *
15
+ * Calling `interopDefault` on a default import unwraps one extra level of
16
+ * `default` nesting, so the same code works whether the dependency was loaded
17
+ * as native ESM (`.mjs`) or through the CommonJS interop (`.cjs`). See #452.
18
+ */
19
+ function interopDefault(value) {
20
+ if (value !== null && typeof value === "object" && "default" in value) return value.default;
21
+ return value;
22
+ }
23
+ //#endregion
24
+ export { interopDefault as t };
25
+
26
+ //# sourceMappingURL=interop.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interop.mjs","names":[],"sources":["../src/interop.ts"],"sourcesContent":["/**\n * Interop helpers for loading ESM-only dependencies from the CommonJS build.\n */\n\n/**\n * Unwrap the `default` export of an ESM-only dependency.\n *\n * The CommonJS build (`dist/index.cjs`) loads pure-ESM packages such as\n * `rehype-parse` and `rehype-stringify` through `require(esm)`. The bundler's\n * interop helper then double-wraps their default export: what should be the\n * plugin function arrives as the module namespace `{ default: fn }` instead of\n * `fn` itself. Passing that object to `unified().use()` throws\n * \"Expected usable value but received an empty preset\".\n *\n * Calling `interopDefault` on a default import unwraps one extra level of\n * `default` nesting, so the same code works whether the dependency was loaded\n * as native ESM (`.mjs`) or through the CommonJS interop (`.cjs`). See #452.\n */\nexport function interopDefault<T>(value: T): T {\n if (value !== null && typeof value === \"object\" && \"default\" in value) {\n return (value as { default: T }).default;\n }\n return value;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,SAAgB,eAAkB,OAAa;CAC7C,IAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,aAAa,OAC9D,OAAQ,MAAyB;CAEnC,OAAO"}
package/dist/ogp.cjs CHANGED
@@ -1,4 +1,5 @@
1
1
  const require_chunk = require("./chunk.cjs");
2
+ const require_interop = require("./interop.cjs");
2
3
  let unified = require("unified");
3
4
  let rehype_parse = require("rehype-parse");
4
5
  rehype_parse = require_chunk.__toESM(rehype_parse, 1);
@@ -18,6 +19,8 @@ var ogp_exports = /* @__PURE__ */ require_chunk.__exportAll({
18
19
  prefetchOgpData: () => prefetchOgpData,
19
20
  transformOgp: () => transformOgp
20
21
  });
22
+ const rehypeParse = require_interop.interopDefault(rehype_parse.default);
23
+ const rehypeStringify = require_interop.interopDefault(rehype_stringify.default);
21
24
  const defaultOptions = {
22
25
  timeout: 1e4,
23
26
  cache: true,
@@ -300,7 +303,7 @@ function rehypeOgp(ogpDataMap) {
300
303
  async function transformOgp(html, ogpDataMap, options) {
301
304
  let dataMap = ogpDataMap;
302
305
  if (!dataMap) dataMap = await prefetchOgpData(await collectOgpUrls(html), options);
303
- const result = await (0, unified.unified)().use(rehype_parse.default, { fragment: true }).use(rehypeOgp, dataMap).use(rehype_stringify.default).process(html);
306
+ const result = await (0, unified.unified)().use(rehypeParse, { fragment: true }).use(rehypeOgp, dataMap).use(rehypeStringify).process(html);
304
307
  return String(result);
305
308
  }
306
309
  //#endregion
package/dist/ogp.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ogp.cjs","names":["rehypeParse","rehypeStringify"],"sources":["../src/plugins/ogp.ts"],"sourcesContent":["/**\n * OGP Card Plugin - Link card embedding\n *\n * Transforms <OgCard> components into static link preview cards\n * by fetching OGP metadata at build time.\n */\n\nimport { unified } from \"unified\";\nimport rehypeParse from \"rehype-parse\";\nimport rehypeStringify from \"rehype-stringify\";\nimport type { Root, Element } from \"hast\";\n\nexport interface OgpData {\n url: string;\n title: string;\n description?: string;\n image?: string;\n siteName?: string;\n favicon?: string;\n}\n\nexport interface OgpOptions {\n /**\n * Request timeout in milliseconds.\n * @default 10000\n */\n timeout?: number;\n\n /**\n * Cache fetched Open Graph metadata in memory for the current process.\n * @default true\n */\n cache?: boolean;\n\n /**\n * Cache TTL in milliseconds.\n * @default 3600000\n */\n cacheTTL?: number;\n\n /**\n * User agent sent with metadata fetch requests.\n * @default 'ox-content-ogp-bot/1.0 (compatible; +https://github.com/ubugeeei-prod/ox-content)'\n */\n userAgent?: string;\n}\n\nconst defaultOptions: Required<OgpOptions> = {\n timeout: 10000,\n cache: true,\n cacheTTL: 3600000,\n userAgent: \"ox-content-ogp-bot/1.0 (compatible; +https://github.com/ubugeeei-prod/ox-content)\",\n};\n\n// Simple in-memory cache\nconst ogpCache = new Map<string, { data: OgpData; timestamp: number }>();\n\nfunction isPrivateIPv4(hostname: string): boolean {\n const parts = hostname.split(\".\").map(Number);\n if (\n parts.length !== 4 ||\n parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)\n ) {\n return false;\n }\n const [a, b] = parts;\n return (\n a === 10 ||\n a === 127 ||\n a === 0 ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 192 && b === 168) ||\n (a === 169 && b === 254)\n );\n}\n\nexport function isSafeOgpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n const host = url.hostname.toLowerCase();\n const ipv6 = host.replace(/^\\[|\\]$/g, \"\");\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return false;\n if (host === \"localhost\" || host.endsWith(\".localhost\")) return false;\n if (\n ipv6.includes(\":\") &&\n (ipv6 === \"::1\" || ipv6.startsWith(\"fc\") || ipv6.startsWith(\"fd\") || ipv6.startsWith(\"fe80\"))\n )\n return false;\n return !isPrivateIPv4(host);\n } catch {\n return false;\n }\n}\n\n/**\n * Get element attribute value.\n */\nfunction getAttribute(el: Element, name: string): string | undefined {\n const value = el.properties?.[name];\n if (typeof value === \"string\") return value;\n if (Array.isArray(value)) return value.join(\" \");\n return undefined;\n}\n\n/**\n * Extract domain from URL.\n */\nfunction extractDomain(url: string): string {\n try {\n const urlObj = new URL(url);\n return urlObj.hostname;\n } catch {\n return url;\n }\n}\n\n/**\n * Get favicon URL for a domain.\n */\nfunction getFaviconUrl(url: string): string {\n try {\n const urlObj = new URL(url);\n // Use Google's favicon service as fallback\n return `https://www.google.com/s2/favicons?domain=${urlObj.hostname}&sz=32`;\n } catch {\n return \"\";\n }\n}\n\n/**\n * Parse OGP metadata from HTML.\n */\nfunction parseOgpFromHtml(html: string, url: string): OgpData {\n const result: OgpData = {\n url,\n title: \"\",\n };\n\n // Extract title\n const titleMatch = html.match(/<title[^>]*>([^<]+)<\\/title>/i);\n const ogTitleMatch =\n html.match(/<meta[^>]*property=[\"']og:title[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:title[\"']/i);\n\n result.title = ogTitleMatch?.[1] || titleMatch?.[1] || extractDomain(url);\n\n // Extract description\n const descMatch =\n html.match(/<meta[^>]*property=[\"']og:description[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:description[\"']/i) ||\n html.match(/<meta[^>]*name=[\"']description[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*name=[\"']description[\"']/i);\n\n if (descMatch) {\n result.description = descMatch[1];\n }\n\n // Extract image\n const imageMatch =\n html.match(/<meta[^>]*property=[\"']og:image[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:image[\"']/i);\n\n if (imageMatch) {\n let imageUrl = imageMatch[1];\n // Handle relative URLs\n if (imageUrl.startsWith(\"/\")) {\n try {\n const urlObj = new URL(url);\n imageUrl = `${urlObj.protocol}//${urlObj.host}${imageUrl}`;\n } catch {\n // Keep as is\n }\n }\n result.image = imageUrl;\n }\n\n // Extract site name\n const siteNameMatch =\n html.match(/<meta[^>]*property=[\"']og:site_name[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:site_name[\"']/i);\n\n if (siteNameMatch) {\n result.siteName = siteNameMatch[1];\n }\n\n // Get favicon\n result.favicon = getFaviconUrl(url);\n\n return result;\n}\n\n/**\n * Fetch OGP data for a URL.\n */\nexport async function fetchOgpData(\n url: string,\n options: Required<OgpOptions>,\n): Promise<OgpData | null> {\n if (!isSafeOgpUrl(url)) {\n return null;\n }\n\n // Check cache\n if (options.cache) {\n const cached = ogpCache.get(url);\n if (cached && Date.now() - cached.timestamp < options.cacheTTL) {\n return cached.data;\n }\n }\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), options.timeout);\n\n const response = await fetch(url, {\n headers: {\n \"User-Agent\": options.userAgent,\n Accept: \"text/html,application/xhtml+xml\",\n },\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n console.warn(`Failed to fetch OGP for ${url}: ${response.status}`);\n return null;\n }\n\n const html = await response.text();\n const data = parseOgpFromHtml(html, url);\n\n // Cache the result\n if (options.cache) {\n ogpCache.set(url, { data, timestamp: Date.now() });\n }\n\n return data;\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") {\n console.warn(`Timeout fetching OGP for ${url}`);\n } else {\n console.warn(`Error fetching OGP for ${url}:`, error);\n }\n return null;\n }\n}\n\n/**\n * Create OGP card element.\n */\nfunction createOgpCard(data: OgpData): Element {\n const children: Element[\"children\"] = [];\n\n // Content section\n const contentChildren: Element[\"children\"] = [];\n\n // Title\n contentChildren.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-title\"] },\n children: [{ type: \"text\", value: data.title }],\n });\n\n // Description\n if (data.description) {\n contentChildren.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-description\"] },\n children: [{ type: \"text\", value: data.description }],\n });\n }\n\n // Meta (favicon + domain)\n const metaChildren: Element[\"children\"] = [];\n\n if (data.favicon) {\n metaChildren.push({\n type: \"element\",\n tagName: \"img\",\n properties: {\n className: [\"ox-ogp-favicon\"],\n src: data.favicon,\n alt: \"\",\n loading: \"lazy\",\n },\n children: [],\n });\n }\n\n metaChildren.push({\n type: \"element\",\n tagName: \"span\",\n properties: { className: [\"ox-ogp-domain\"] },\n children: [{ type: \"text\", value: data.siteName || extractDomain(data.url) }],\n });\n\n contentChildren.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-meta\"] },\n children: metaChildren,\n });\n\n children.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-content\"] },\n children: contentChildren,\n });\n\n // Image\n if (data.image) {\n children.push({\n type: \"element\",\n tagName: \"img\",\n properties: {\n className: [\"ox-ogp-image\"],\n src: data.image,\n alt: \"\",\n loading: \"lazy\",\n },\n children: [],\n });\n }\n\n return {\n type: \"element\",\n tagName: \"a\",\n properties: {\n className: [\"ox-ogp-card\"],\n href: isSafeOgpUrl(data.url) ? data.url : \"#\",\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n },\n children,\n };\n}\n\n/**\n * Create fallback element when OGP data is unavailable.\n */\nfunction createFallbackCard(url: string): Element {\n return {\n type: \"element\",\n tagName: \"a\",\n properties: {\n className: [\"ox-ogp-simple\"],\n href: isSafeOgpUrl(url) ? url : \"#\",\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n },\n children: [\n {\n type: \"element\",\n tagName: \"svg\",\n properties: {\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n \"stroke-width\": \"2\",\n },\n children: [\n {\n type: \"element\",\n tagName: \"path\",\n properties: {\n d: \"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3\",\n },\n children: [],\n },\n ],\n },\n { type: \"text\", value: extractDomain(url) },\n ],\n };\n}\n\n/**\n * Collect all OGP URLs from HTML for pre-fetching.\n */\nexport async function collectOgpUrls(html: string): Promise<string[]> {\n const urls: string[] = [];\n const urlPattern = /<ogcard[^>]*\\s+url=[\"']([^\"']+)[\"']/gi;\n\n let match;\n while ((match = urlPattern.exec(html)) !== null) {\n if (isSafeOgpUrl(match[1])) {\n urls.push(match[1]);\n }\n }\n\n return urls;\n}\n\n/**\n * Pre-fetch all OGP data.\n */\nexport async function prefetchOgpData(\n urls: string[],\n options?: OgpOptions,\n): Promise<Map<string, OgpData | null>> {\n const mergedOptions = { ...defaultOptions, ...options };\n const results = new Map<string, OgpData | null>();\n\n await Promise.all(\n urls.map(async (url) => {\n const data = await fetchOgpData(url, mergedOptions);\n results.set(url, data);\n }),\n );\n\n return results;\n}\n\n/**\n * Rehype plugin to transform OgCard components.\n */\nfunction rehypeOgp(ogpDataMap: Map<string, OgpData | null>) {\n return (tree: Root) => {\n const visit = (node: Root | Element) => {\n if (\"children\" in node) {\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i];\n\n if (child.type === \"element\") {\n // Check for <OgCard> component\n if (child.tagName.toLowerCase() === \"ogcard\") {\n const url = getAttribute(child, \"url\");\n\n if (url) {\n const ogpData = ogpDataMap.get(url);\n const cardElement = ogpData ? createOgpCard(ogpData) : createFallbackCard(url);\n node.children[i] = cardElement;\n }\n } else {\n visit(child);\n }\n }\n }\n }\n };\n\n visit(tree);\n };\n}\n\n/**\n * Transform OgCard components in HTML.\n */\nexport async function transformOgp(\n html: string,\n ogpDataMap?: Map<string, OgpData | null>,\n options?: OgpOptions,\n): Promise<string> {\n // If no pre-fetched data, collect and fetch\n let dataMap = ogpDataMap;\n if (!dataMap) {\n const urls = await collectOgpUrls(html);\n dataMap = await prefetchOgpData(urls, options);\n }\n\n const result = await unified()\n .use(rehypeParse, { fragment: true })\n .use(rehypeOgp, dataMap)\n .use(rehypeStringify)\n .process(html);\n\n return String(result);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ogp.cjs","names":["interopDefault","rehypeParsePlugin","rehypeStringifyPlugin"],"sources":["../src/plugins/ogp.ts"],"sourcesContent":["/**\n * OGP Card Plugin - Link card embedding\n *\n * Transforms <OgCard> components into static link preview cards\n * by fetching OGP metadata at build time.\n */\n\nimport { unified } from \"unified\";\nimport rehypeParsePlugin from \"rehype-parse\";\nimport rehypeStringifyPlugin from \"rehype-stringify\";\nimport type { Root, Element } from \"hast\";\nimport { interopDefault } from \"../interop\";\n\n// ESM-only plugins are double-wrapped by the CommonJS interop; unwrap. See #452.\nconst rehypeParse = interopDefault(rehypeParsePlugin);\nconst rehypeStringify = interopDefault(rehypeStringifyPlugin);\n\nexport interface OgpData {\n url: string;\n title: string;\n description?: string;\n image?: string;\n siteName?: string;\n favicon?: string;\n}\n\nexport interface OgpOptions {\n /**\n * Request timeout in milliseconds.\n * @default 10000\n */\n timeout?: number;\n\n /**\n * Cache fetched Open Graph metadata in memory for the current process.\n * @default true\n */\n cache?: boolean;\n\n /**\n * Cache TTL in milliseconds.\n * @default 3600000\n */\n cacheTTL?: number;\n\n /**\n * User agent sent with metadata fetch requests.\n * @default 'ox-content-ogp-bot/1.0 (compatible; +https://github.com/ubugeeei-prod/ox-content)'\n */\n userAgent?: string;\n}\n\nconst defaultOptions: Required<OgpOptions> = {\n timeout: 10000,\n cache: true,\n cacheTTL: 3600000,\n userAgent: \"ox-content-ogp-bot/1.0 (compatible; +https://github.com/ubugeeei-prod/ox-content)\",\n};\n\n// Simple in-memory cache\nconst ogpCache = new Map<string, { data: OgpData; timestamp: number }>();\n\nfunction isPrivateIPv4(hostname: string): boolean {\n const parts = hostname.split(\".\").map(Number);\n if (\n parts.length !== 4 ||\n parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)\n ) {\n return false;\n }\n const [a, b] = parts;\n return (\n a === 10 ||\n a === 127 ||\n a === 0 ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 192 && b === 168) ||\n (a === 169 && b === 254)\n );\n}\n\nexport function isSafeOgpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n const host = url.hostname.toLowerCase();\n const ipv6 = host.replace(/^\\[|\\]$/g, \"\");\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return false;\n if (host === \"localhost\" || host.endsWith(\".localhost\")) return false;\n if (\n ipv6.includes(\":\") &&\n (ipv6 === \"::1\" || ipv6.startsWith(\"fc\") || ipv6.startsWith(\"fd\") || ipv6.startsWith(\"fe80\"))\n )\n return false;\n return !isPrivateIPv4(host);\n } catch {\n return false;\n }\n}\n\n/**\n * Get element attribute value.\n */\nfunction getAttribute(el: Element, name: string): string | undefined {\n const value = el.properties?.[name];\n if (typeof value === \"string\") return value;\n if (Array.isArray(value)) return value.join(\" \");\n return undefined;\n}\n\n/**\n * Extract domain from URL.\n */\nfunction extractDomain(url: string): string {\n try {\n const urlObj = new URL(url);\n return urlObj.hostname;\n } catch {\n return url;\n }\n}\n\n/**\n * Get favicon URL for a domain.\n */\nfunction getFaviconUrl(url: string): string {\n try {\n const urlObj = new URL(url);\n // Use Google's favicon service as fallback\n return `https://www.google.com/s2/favicons?domain=${urlObj.hostname}&sz=32`;\n } catch {\n return \"\";\n }\n}\n\n/**\n * Parse OGP metadata from HTML.\n */\nfunction parseOgpFromHtml(html: string, url: string): OgpData {\n const result: OgpData = {\n url,\n title: \"\",\n };\n\n // Extract title\n const titleMatch = html.match(/<title[^>]*>([^<]+)<\\/title>/i);\n const ogTitleMatch =\n html.match(/<meta[^>]*property=[\"']og:title[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:title[\"']/i);\n\n result.title = ogTitleMatch?.[1] || titleMatch?.[1] || extractDomain(url);\n\n // Extract description\n const descMatch =\n html.match(/<meta[^>]*property=[\"']og:description[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:description[\"']/i) ||\n html.match(/<meta[^>]*name=[\"']description[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*name=[\"']description[\"']/i);\n\n if (descMatch) {\n result.description = descMatch[1];\n }\n\n // Extract image\n const imageMatch =\n html.match(/<meta[^>]*property=[\"']og:image[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:image[\"']/i);\n\n if (imageMatch) {\n let imageUrl = imageMatch[1];\n // Handle relative URLs\n if (imageUrl.startsWith(\"/\")) {\n try {\n const urlObj = new URL(url);\n imageUrl = `${urlObj.protocol}//${urlObj.host}${imageUrl}`;\n } catch {\n // Keep as is\n }\n }\n result.image = imageUrl;\n }\n\n // Extract site name\n const siteNameMatch =\n html.match(/<meta[^>]*property=[\"']og:site_name[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:site_name[\"']/i);\n\n if (siteNameMatch) {\n result.siteName = siteNameMatch[1];\n }\n\n // Get favicon\n result.favicon = getFaviconUrl(url);\n\n return result;\n}\n\n/**\n * Fetch OGP data for a URL.\n */\nexport async function fetchOgpData(\n url: string,\n options: Required<OgpOptions>,\n): Promise<OgpData | null> {\n if (!isSafeOgpUrl(url)) {\n return null;\n }\n\n // Check cache\n if (options.cache) {\n const cached = ogpCache.get(url);\n if (cached && Date.now() - cached.timestamp < options.cacheTTL) {\n return cached.data;\n }\n }\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), options.timeout);\n\n const response = await fetch(url, {\n headers: {\n \"User-Agent\": options.userAgent,\n Accept: \"text/html,application/xhtml+xml\",\n },\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n console.warn(`Failed to fetch OGP for ${url}: ${response.status}`);\n return null;\n }\n\n const html = await response.text();\n const data = parseOgpFromHtml(html, url);\n\n // Cache the result\n if (options.cache) {\n ogpCache.set(url, { data, timestamp: Date.now() });\n }\n\n return data;\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") {\n console.warn(`Timeout fetching OGP for ${url}`);\n } else {\n console.warn(`Error fetching OGP for ${url}:`, error);\n }\n return null;\n }\n}\n\n/**\n * Create OGP card element.\n */\nfunction createOgpCard(data: OgpData): Element {\n const children: Element[\"children\"] = [];\n\n // Content section\n const contentChildren: Element[\"children\"] = [];\n\n // Title\n contentChildren.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-title\"] },\n children: [{ type: \"text\", value: data.title }],\n });\n\n // Description\n if (data.description) {\n contentChildren.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-description\"] },\n children: [{ type: \"text\", value: data.description }],\n });\n }\n\n // Meta (favicon + domain)\n const metaChildren: Element[\"children\"] = [];\n\n if (data.favicon) {\n metaChildren.push({\n type: \"element\",\n tagName: \"img\",\n properties: {\n className: [\"ox-ogp-favicon\"],\n src: data.favicon,\n alt: \"\",\n loading: \"lazy\",\n },\n children: [],\n });\n }\n\n metaChildren.push({\n type: \"element\",\n tagName: \"span\",\n properties: { className: [\"ox-ogp-domain\"] },\n children: [{ type: \"text\", value: data.siteName || extractDomain(data.url) }],\n });\n\n contentChildren.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-meta\"] },\n children: metaChildren,\n });\n\n children.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-content\"] },\n children: contentChildren,\n });\n\n // Image\n if (data.image) {\n children.push({\n type: \"element\",\n tagName: \"img\",\n properties: {\n className: [\"ox-ogp-image\"],\n src: data.image,\n alt: \"\",\n loading: \"lazy\",\n },\n children: [],\n });\n }\n\n return {\n type: \"element\",\n tagName: \"a\",\n properties: {\n className: [\"ox-ogp-card\"],\n href: isSafeOgpUrl(data.url) ? data.url : \"#\",\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n },\n children,\n };\n}\n\n/**\n * Create fallback element when OGP data is unavailable.\n */\nfunction createFallbackCard(url: string): Element {\n return {\n type: \"element\",\n tagName: \"a\",\n properties: {\n className: [\"ox-ogp-simple\"],\n href: isSafeOgpUrl(url) ? url : \"#\",\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n },\n children: [\n {\n type: \"element\",\n tagName: \"svg\",\n properties: {\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n \"stroke-width\": \"2\",\n },\n children: [\n {\n type: \"element\",\n tagName: \"path\",\n properties: {\n d: \"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3\",\n },\n children: [],\n },\n ],\n },\n { type: \"text\", value: extractDomain(url) },\n ],\n };\n}\n\n/**\n * Collect all OGP URLs from HTML for pre-fetching.\n */\nexport async function collectOgpUrls(html: string): Promise<string[]> {\n const urls: string[] = [];\n const urlPattern = /<ogcard[^>]*\\s+url=[\"']([^\"']+)[\"']/gi;\n\n let match;\n while ((match = urlPattern.exec(html)) !== null) {\n if (isSafeOgpUrl(match[1])) {\n urls.push(match[1]);\n }\n }\n\n return urls;\n}\n\n/**\n * Pre-fetch all OGP data.\n */\nexport async function prefetchOgpData(\n urls: string[],\n options?: OgpOptions,\n): Promise<Map<string, OgpData | null>> {\n const mergedOptions = { ...defaultOptions, ...options };\n const results = new Map<string, OgpData | null>();\n\n await Promise.all(\n urls.map(async (url) => {\n const data = await fetchOgpData(url, mergedOptions);\n results.set(url, data);\n }),\n );\n\n return results;\n}\n\n/**\n * Rehype plugin to transform OgCard components.\n */\nfunction rehypeOgp(ogpDataMap: Map<string, OgpData | null>) {\n return (tree: Root) => {\n const visit = (node: Root | Element) => {\n if (\"children\" in node) {\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i];\n\n if (child.type === \"element\") {\n // Check for <OgCard> component\n if (child.tagName.toLowerCase() === \"ogcard\") {\n const url = getAttribute(child, \"url\");\n\n if (url) {\n const ogpData = ogpDataMap.get(url);\n const cardElement = ogpData ? createOgpCard(ogpData) : createFallbackCard(url);\n node.children[i] = cardElement;\n }\n } else {\n visit(child);\n }\n }\n }\n }\n };\n\n visit(tree);\n };\n}\n\n/**\n * Transform OgCard components in HTML.\n */\nexport async function transformOgp(\n html: string,\n ogpDataMap?: Map<string, OgpData | null>,\n options?: OgpOptions,\n): Promise<string> {\n // If no pre-fetched data, collect and fetch\n let dataMap = ogpDataMap;\n if (!dataMap) {\n const urls = await collectOgpUrls(html);\n dataMap = await prefetchOgpData(urls, options);\n }\n\n const result = await unified()\n .use(rehypeParse, { fragment: true })\n .use(rehypeOgp, dataMap)\n .use(rehypeStringify)\n .process(html);\n\n return String(result);\n}\n"],"mappings":"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"}
package/dist/ogp2.mjs CHANGED
@@ -1,6 +1,7 @@
1
+ import { t as interopDefault } from "./interop.mjs";
1
2
  import { unified } from "unified";
2
- import rehypeParse from "rehype-parse";
3
- import rehypeStringify from "rehype-stringify";
3
+ import rehypeParsePlugin from "rehype-parse";
4
+ import rehypeStringifyPlugin from "rehype-stringify";
4
5
  //#region src/plugins/ogp.ts
5
6
  /**
6
7
  * OGP Card Plugin - Link card embedding
@@ -8,6 +9,8 @@ import rehypeStringify from "rehype-stringify";
8
9
  * Transforms <OgCard> components into static link preview cards
9
10
  * by fetching OGP metadata at build time.
10
11
  */
12
+ const rehypeParse = interopDefault(rehypeParsePlugin);
13
+ const rehypeStringify = interopDefault(rehypeStringifyPlugin);
11
14
  const defaultOptions = {
12
15
  timeout: 1e4,
13
16
  cache: true,
package/dist/ogp2.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ogp2.mjs","names":[],"sources":["../src/plugins/ogp.ts"],"sourcesContent":["/**\n * OGP Card Plugin - Link card embedding\n *\n * Transforms <OgCard> components into static link preview cards\n * by fetching OGP metadata at build time.\n */\n\nimport { unified } from \"unified\";\nimport rehypeParse from \"rehype-parse\";\nimport rehypeStringify from \"rehype-stringify\";\nimport type { Root, Element } from \"hast\";\n\nexport interface OgpData {\n url: string;\n title: string;\n description?: string;\n image?: string;\n siteName?: string;\n favicon?: string;\n}\n\nexport interface OgpOptions {\n /**\n * Request timeout in milliseconds.\n * @default 10000\n */\n timeout?: number;\n\n /**\n * Cache fetched Open Graph metadata in memory for the current process.\n * @default true\n */\n cache?: boolean;\n\n /**\n * Cache TTL in milliseconds.\n * @default 3600000\n */\n cacheTTL?: number;\n\n /**\n * User agent sent with metadata fetch requests.\n * @default 'ox-content-ogp-bot/1.0 (compatible; +https://github.com/ubugeeei-prod/ox-content)'\n */\n userAgent?: string;\n}\n\nconst defaultOptions: Required<OgpOptions> = {\n timeout: 10000,\n cache: true,\n cacheTTL: 3600000,\n userAgent: \"ox-content-ogp-bot/1.0 (compatible; +https://github.com/ubugeeei-prod/ox-content)\",\n};\n\n// Simple in-memory cache\nconst ogpCache = new Map<string, { data: OgpData; timestamp: number }>();\n\nfunction isPrivateIPv4(hostname: string): boolean {\n const parts = hostname.split(\".\").map(Number);\n if (\n parts.length !== 4 ||\n parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)\n ) {\n return false;\n }\n const [a, b] = parts;\n return (\n a === 10 ||\n a === 127 ||\n a === 0 ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 192 && b === 168) ||\n (a === 169 && b === 254)\n );\n}\n\nexport function isSafeOgpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n const host = url.hostname.toLowerCase();\n const ipv6 = host.replace(/^\\[|\\]$/g, \"\");\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return false;\n if (host === \"localhost\" || host.endsWith(\".localhost\")) return false;\n if (\n ipv6.includes(\":\") &&\n (ipv6 === \"::1\" || ipv6.startsWith(\"fc\") || ipv6.startsWith(\"fd\") || ipv6.startsWith(\"fe80\"))\n )\n return false;\n return !isPrivateIPv4(host);\n } catch {\n return false;\n }\n}\n\n/**\n * Get element attribute value.\n */\nfunction getAttribute(el: Element, name: string): string | undefined {\n const value = el.properties?.[name];\n if (typeof value === \"string\") return value;\n if (Array.isArray(value)) return value.join(\" \");\n return undefined;\n}\n\n/**\n * Extract domain from URL.\n */\nfunction extractDomain(url: string): string {\n try {\n const urlObj = new URL(url);\n return urlObj.hostname;\n } catch {\n return url;\n }\n}\n\n/**\n * Get favicon URL for a domain.\n */\nfunction getFaviconUrl(url: string): string {\n try {\n const urlObj = new URL(url);\n // Use Google's favicon service as fallback\n return `https://www.google.com/s2/favicons?domain=${urlObj.hostname}&sz=32`;\n } catch {\n return \"\";\n }\n}\n\n/**\n * Parse OGP metadata from HTML.\n */\nfunction parseOgpFromHtml(html: string, url: string): OgpData {\n const result: OgpData = {\n url,\n title: \"\",\n };\n\n // Extract title\n const titleMatch = html.match(/<title[^>]*>([^<]+)<\\/title>/i);\n const ogTitleMatch =\n html.match(/<meta[^>]*property=[\"']og:title[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:title[\"']/i);\n\n result.title = ogTitleMatch?.[1] || titleMatch?.[1] || extractDomain(url);\n\n // Extract description\n const descMatch =\n html.match(/<meta[^>]*property=[\"']og:description[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:description[\"']/i) ||\n html.match(/<meta[^>]*name=[\"']description[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*name=[\"']description[\"']/i);\n\n if (descMatch) {\n result.description = descMatch[1];\n }\n\n // Extract image\n const imageMatch =\n html.match(/<meta[^>]*property=[\"']og:image[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:image[\"']/i);\n\n if (imageMatch) {\n let imageUrl = imageMatch[1];\n // Handle relative URLs\n if (imageUrl.startsWith(\"/\")) {\n try {\n const urlObj = new URL(url);\n imageUrl = `${urlObj.protocol}//${urlObj.host}${imageUrl}`;\n } catch {\n // Keep as is\n }\n }\n result.image = imageUrl;\n }\n\n // Extract site name\n const siteNameMatch =\n html.match(/<meta[^>]*property=[\"']og:site_name[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:site_name[\"']/i);\n\n if (siteNameMatch) {\n result.siteName = siteNameMatch[1];\n }\n\n // Get favicon\n result.favicon = getFaviconUrl(url);\n\n return result;\n}\n\n/**\n * Fetch OGP data for a URL.\n */\nexport async function fetchOgpData(\n url: string,\n options: Required<OgpOptions>,\n): Promise<OgpData | null> {\n if (!isSafeOgpUrl(url)) {\n return null;\n }\n\n // Check cache\n if (options.cache) {\n const cached = ogpCache.get(url);\n if (cached && Date.now() - cached.timestamp < options.cacheTTL) {\n return cached.data;\n }\n }\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), options.timeout);\n\n const response = await fetch(url, {\n headers: {\n \"User-Agent\": options.userAgent,\n Accept: \"text/html,application/xhtml+xml\",\n },\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n console.warn(`Failed to fetch OGP for ${url}: ${response.status}`);\n return null;\n }\n\n const html = await response.text();\n const data = parseOgpFromHtml(html, url);\n\n // Cache the result\n if (options.cache) {\n ogpCache.set(url, { data, timestamp: Date.now() });\n }\n\n return data;\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") {\n console.warn(`Timeout fetching OGP for ${url}`);\n } else {\n console.warn(`Error fetching OGP for ${url}:`, error);\n }\n return null;\n }\n}\n\n/**\n * Create OGP card element.\n */\nfunction createOgpCard(data: OgpData): Element {\n const children: Element[\"children\"] = [];\n\n // Content section\n const contentChildren: Element[\"children\"] = [];\n\n // Title\n contentChildren.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-title\"] },\n children: [{ type: \"text\", value: data.title }],\n });\n\n // Description\n if (data.description) {\n contentChildren.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-description\"] },\n children: [{ type: \"text\", value: data.description }],\n });\n }\n\n // Meta (favicon + domain)\n const metaChildren: Element[\"children\"] = [];\n\n if (data.favicon) {\n metaChildren.push({\n type: \"element\",\n tagName: \"img\",\n properties: {\n className: [\"ox-ogp-favicon\"],\n src: data.favicon,\n alt: \"\",\n loading: \"lazy\",\n },\n children: [],\n });\n }\n\n metaChildren.push({\n type: \"element\",\n tagName: \"span\",\n properties: { className: [\"ox-ogp-domain\"] },\n children: [{ type: \"text\", value: data.siteName || extractDomain(data.url) }],\n });\n\n contentChildren.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-meta\"] },\n children: metaChildren,\n });\n\n children.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-content\"] },\n children: contentChildren,\n });\n\n // Image\n if (data.image) {\n children.push({\n type: \"element\",\n tagName: \"img\",\n properties: {\n className: [\"ox-ogp-image\"],\n src: data.image,\n alt: \"\",\n loading: \"lazy\",\n },\n children: [],\n });\n }\n\n return {\n type: \"element\",\n tagName: \"a\",\n properties: {\n className: [\"ox-ogp-card\"],\n href: isSafeOgpUrl(data.url) ? data.url : \"#\",\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n },\n children,\n };\n}\n\n/**\n * Create fallback element when OGP data is unavailable.\n */\nfunction createFallbackCard(url: string): Element {\n return {\n type: \"element\",\n tagName: \"a\",\n properties: {\n className: [\"ox-ogp-simple\"],\n href: isSafeOgpUrl(url) ? url : \"#\",\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n },\n children: [\n {\n type: \"element\",\n tagName: \"svg\",\n properties: {\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n \"stroke-width\": \"2\",\n },\n children: [\n {\n type: \"element\",\n tagName: \"path\",\n properties: {\n d: \"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3\",\n },\n children: [],\n },\n ],\n },\n { type: \"text\", value: extractDomain(url) },\n ],\n };\n}\n\n/**\n * Collect all OGP URLs from HTML for pre-fetching.\n */\nexport async function collectOgpUrls(html: string): Promise<string[]> {\n const urls: string[] = [];\n const urlPattern = /<ogcard[^>]*\\s+url=[\"']([^\"']+)[\"']/gi;\n\n let match;\n while ((match = urlPattern.exec(html)) !== null) {\n if (isSafeOgpUrl(match[1])) {\n urls.push(match[1]);\n }\n }\n\n return urls;\n}\n\n/**\n * Pre-fetch all OGP data.\n */\nexport async function prefetchOgpData(\n urls: string[],\n options?: OgpOptions,\n): Promise<Map<string, OgpData | null>> {\n const mergedOptions = { ...defaultOptions, ...options };\n const results = new Map<string, OgpData | null>();\n\n await Promise.all(\n urls.map(async (url) => {\n const data = await fetchOgpData(url, mergedOptions);\n results.set(url, data);\n }),\n );\n\n return results;\n}\n\n/**\n * Rehype plugin to transform OgCard components.\n */\nfunction rehypeOgp(ogpDataMap: Map<string, OgpData | null>) {\n return (tree: Root) => {\n const visit = (node: Root | Element) => {\n if (\"children\" in node) {\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i];\n\n if (child.type === \"element\") {\n // Check for <OgCard> component\n if (child.tagName.toLowerCase() === \"ogcard\") {\n const url = getAttribute(child, \"url\");\n\n if (url) {\n const ogpData = ogpDataMap.get(url);\n const cardElement = ogpData ? createOgpCard(ogpData) : createFallbackCard(url);\n node.children[i] = cardElement;\n }\n } else {\n visit(child);\n }\n }\n }\n }\n };\n\n visit(tree);\n };\n}\n\n/**\n * Transform OgCard components in HTML.\n */\nexport async function transformOgp(\n html: string,\n ogpDataMap?: Map<string, OgpData | null>,\n options?: OgpOptions,\n): Promise<string> {\n // If no pre-fetched data, collect and fetch\n let dataMap = ogpDataMap;\n if (!dataMap) {\n const urls = await collectOgpUrls(html);\n dataMap = await prefetchOgpData(urls, options);\n }\n\n const result = await unified()\n .use(rehypeParse, { fragment: true })\n .use(rehypeOgp, dataMap)\n .use(rehypeStringify)\n .process(html);\n\n return String(result);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ogp2.mjs","names":[],"sources":["../src/plugins/ogp.ts"],"sourcesContent":["/**\n * OGP Card Plugin - Link card embedding\n *\n * Transforms <OgCard> components into static link preview cards\n * by fetching OGP metadata at build time.\n */\n\nimport { unified } from \"unified\";\nimport rehypeParsePlugin from \"rehype-parse\";\nimport rehypeStringifyPlugin from \"rehype-stringify\";\nimport type { Root, Element } from \"hast\";\nimport { interopDefault } from \"../interop\";\n\n// ESM-only plugins are double-wrapped by the CommonJS interop; unwrap. See #452.\nconst rehypeParse = interopDefault(rehypeParsePlugin);\nconst rehypeStringify = interopDefault(rehypeStringifyPlugin);\n\nexport interface OgpData {\n url: string;\n title: string;\n description?: string;\n image?: string;\n siteName?: string;\n favicon?: string;\n}\n\nexport interface OgpOptions {\n /**\n * Request timeout in milliseconds.\n * @default 10000\n */\n timeout?: number;\n\n /**\n * Cache fetched Open Graph metadata in memory for the current process.\n * @default true\n */\n cache?: boolean;\n\n /**\n * Cache TTL in milliseconds.\n * @default 3600000\n */\n cacheTTL?: number;\n\n /**\n * User agent sent with metadata fetch requests.\n * @default 'ox-content-ogp-bot/1.0 (compatible; +https://github.com/ubugeeei-prod/ox-content)'\n */\n userAgent?: string;\n}\n\nconst defaultOptions: Required<OgpOptions> = {\n timeout: 10000,\n cache: true,\n cacheTTL: 3600000,\n userAgent: \"ox-content-ogp-bot/1.0 (compatible; +https://github.com/ubugeeei-prod/ox-content)\",\n};\n\n// Simple in-memory cache\nconst ogpCache = new Map<string, { data: OgpData; timestamp: number }>();\n\nfunction isPrivateIPv4(hostname: string): boolean {\n const parts = hostname.split(\".\").map(Number);\n if (\n parts.length !== 4 ||\n parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)\n ) {\n return false;\n }\n const [a, b] = parts;\n return (\n a === 10 ||\n a === 127 ||\n a === 0 ||\n (a === 172 && b >= 16 && b <= 31) ||\n (a === 192 && b === 168) ||\n (a === 169 && b === 254)\n );\n}\n\nexport function isSafeOgpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n const host = url.hostname.toLowerCase();\n const ipv6 = host.replace(/^\\[|\\]$/g, \"\");\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return false;\n if (host === \"localhost\" || host.endsWith(\".localhost\")) return false;\n if (\n ipv6.includes(\":\") &&\n (ipv6 === \"::1\" || ipv6.startsWith(\"fc\") || ipv6.startsWith(\"fd\") || ipv6.startsWith(\"fe80\"))\n )\n return false;\n return !isPrivateIPv4(host);\n } catch {\n return false;\n }\n}\n\n/**\n * Get element attribute value.\n */\nfunction getAttribute(el: Element, name: string): string | undefined {\n const value = el.properties?.[name];\n if (typeof value === \"string\") return value;\n if (Array.isArray(value)) return value.join(\" \");\n return undefined;\n}\n\n/**\n * Extract domain from URL.\n */\nfunction extractDomain(url: string): string {\n try {\n const urlObj = new URL(url);\n return urlObj.hostname;\n } catch {\n return url;\n }\n}\n\n/**\n * Get favicon URL for a domain.\n */\nfunction getFaviconUrl(url: string): string {\n try {\n const urlObj = new URL(url);\n // Use Google's favicon service as fallback\n return `https://www.google.com/s2/favicons?domain=${urlObj.hostname}&sz=32`;\n } catch {\n return \"\";\n }\n}\n\n/**\n * Parse OGP metadata from HTML.\n */\nfunction parseOgpFromHtml(html: string, url: string): OgpData {\n const result: OgpData = {\n url,\n title: \"\",\n };\n\n // Extract title\n const titleMatch = html.match(/<title[^>]*>([^<]+)<\\/title>/i);\n const ogTitleMatch =\n html.match(/<meta[^>]*property=[\"']og:title[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:title[\"']/i);\n\n result.title = ogTitleMatch?.[1] || titleMatch?.[1] || extractDomain(url);\n\n // Extract description\n const descMatch =\n html.match(/<meta[^>]*property=[\"']og:description[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:description[\"']/i) ||\n html.match(/<meta[^>]*name=[\"']description[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*name=[\"']description[\"']/i);\n\n if (descMatch) {\n result.description = descMatch[1];\n }\n\n // Extract image\n const imageMatch =\n html.match(/<meta[^>]*property=[\"']og:image[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:image[\"']/i);\n\n if (imageMatch) {\n let imageUrl = imageMatch[1];\n // Handle relative URLs\n if (imageUrl.startsWith(\"/\")) {\n try {\n const urlObj = new URL(url);\n imageUrl = `${urlObj.protocol}//${urlObj.host}${imageUrl}`;\n } catch {\n // Keep as is\n }\n }\n result.image = imageUrl;\n }\n\n // Extract site name\n const siteNameMatch =\n html.match(/<meta[^>]*property=[\"']og:site_name[\"'][^>]*content=[\"']([^\"']+)[\"']/i) ||\n html.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*property=[\"']og:site_name[\"']/i);\n\n if (siteNameMatch) {\n result.siteName = siteNameMatch[1];\n }\n\n // Get favicon\n result.favicon = getFaviconUrl(url);\n\n return result;\n}\n\n/**\n * Fetch OGP data for a URL.\n */\nexport async function fetchOgpData(\n url: string,\n options: Required<OgpOptions>,\n): Promise<OgpData | null> {\n if (!isSafeOgpUrl(url)) {\n return null;\n }\n\n // Check cache\n if (options.cache) {\n const cached = ogpCache.get(url);\n if (cached && Date.now() - cached.timestamp < options.cacheTTL) {\n return cached.data;\n }\n }\n\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), options.timeout);\n\n const response = await fetch(url, {\n headers: {\n \"User-Agent\": options.userAgent,\n Accept: \"text/html,application/xhtml+xml\",\n },\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n console.warn(`Failed to fetch OGP for ${url}: ${response.status}`);\n return null;\n }\n\n const html = await response.text();\n const data = parseOgpFromHtml(html, url);\n\n // Cache the result\n if (options.cache) {\n ogpCache.set(url, { data, timestamp: Date.now() });\n }\n\n return data;\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") {\n console.warn(`Timeout fetching OGP for ${url}`);\n } else {\n console.warn(`Error fetching OGP for ${url}:`, error);\n }\n return null;\n }\n}\n\n/**\n * Create OGP card element.\n */\nfunction createOgpCard(data: OgpData): Element {\n const children: Element[\"children\"] = [];\n\n // Content section\n const contentChildren: Element[\"children\"] = [];\n\n // Title\n contentChildren.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-title\"] },\n children: [{ type: \"text\", value: data.title }],\n });\n\n // Description\n if (data.description) {\n contentChildren.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-description\"] },\n children: [{ type: \"text\", value: data.description }],\n });\n }\n\n // Meta (favicon + domain)\n const metaChildren: Element[\"children\"] = [];\n\n if (data.favicon) {\n metaChildren.push({\n type: \"element\",\n tagName: \"img\",\n properties: {\n className: [\"ox-ogp-favicon\"],\n src: data.favicon,\n alt: \"\",\n loading: \"lazy\",\n },\n children: [],\n });\n }\n\n metaChildren.push({\n type: \"element\",\n tagName: \"span\",\n properties: { className: [\"ox-ogp-domain\"] },\n children: [{ type: \"text\", value: data.siteName || extractDomain(data.url) }],\n });\n\n contentChildren.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-meta\"] },\n children: metaChildren,\n });\n\n children.push({\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"ox-ogp-content\"] },\n children: contentChildren,\n });\n\n // Image\n if (data.image) {\n children.push({\n type: \"element\",\n tagName: \"img\",\n properties: {\n className: [\"ox-ogp-image\"],\n src: data.image,\n alt: \"\",\n loading: \"lazy\",\n },\n children: [],\n });\n }\n\n return {\n type: \"element\",\n tagName: \"a\",\n properties: {\n className: [\"ox-ogp-card\"],\n href: isSafeOgpUrl(data.url) ? data.url : \"#\",\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n },\n children,\n };\n}\n\n/**\n * Create fallback element when OGP data is unavailable.\n */\nfunction createFallbackCard(url: string): Element {\n return {\n type: \"element\",\n tagName: \"a\",\n properties: {\n className: [\"ox-ogp-simple\"],\n href: isSafeOgpUrl(url) ? url : \"#\",\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n },\n children: [\n {\n type: \"element\",\n tagName: \"svg\",\n properties: {\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n \"stroke-width\": \"2\",\n },\n children: [\n {\n type: \"element\",\n tagName: \"path\",\n properties: {\n d: \"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3\",\n },\n children: [],\n },\n ],\n },\n { type: \"text\", value: extractDomain(url) },\n ],\n };\n}\n\n/**\n * Collect all OGP URLs from HTML for pre-fetching.\n */\nexport async function collectOgpUrls(html: string): Promise<string[]> {\n const urls: string[] = [];\n const urlPattern = /<ogcard[^>]*\\s+url=[\"']([^\"']+)[\"']/gi;\n\n let match;\n while ((match = urlPattern.exec(html)) !== null) {\n if (isSafeOgpUrl(match[1])) {\n urls.push(match[1]);\n }\n }\n\n return urls;\n}\n\n/**\n * Pre-fetch all OGP data.\n */\nexport async function prefetchOgpData(\n urls: string[],\n options?: OgpOptions,\n): Promise<Map<string, OgpData | null>> {\n const mergedOptions = { ...defaultOptions, ...options };\n const results = new Map<string, OgpData | null>();\n\n await Promise.all(\n urls.map(async (url) => {\n const data = await fetchOgpData(url, mergedOptions);\n results.set(url, data);\n }),\n );\n\n return results;\n}\n\n/**\n * Rehype plugin to transform OgCard components.\n */\nfunction rehypeOgp(ogpDataMap: Map<string, OgpData | null>) {\n return (tree: Root) => {\n const visit = (node: Root | Element) => {\n if (\"children\" in node) {\n for (let i = 0; i < node.children.length; i++) {\n const child = node.children[i];\n\n if (child.type === \"element\") {\n // Check for <OgCard> component\n if (child.tagName.toLowerCase() === \"ogcard\") {\n const url = getAttribute(child, \"url\");\n\n if (url) {\n const ogpData = ogpDataMap.get(url);\n const cardElement = ogpData ? createOgpCard(ogpData) : createFallbackCard(url);\n node.children[i] = cardElement;\n }\n } else {\n visit(child);\n }\n }\n }\n }\n };\n\n visit(tree);\n };\n}\n\n/**\n * Transform OgCard components in HTML.\n */\nexport async function transformOgp(\n html: string,\n ogpDataMap?: Map<string, OgpData | null>,\n options?: OgpOptions,\n): Promise<string> {\n // If no pre-fetched data, collect and fetch\n let dataMap = ogpDataMap;\n if (!dataMap) {\n const urls = await collectOgpUrls(html);\n dataMap = await prefetchOgpData(urls, options);\n }\n\n const result = await unified()\n .use(rehypeParse, { fragment: true })\n .use(rehypeOgp, dataMap)\n .use(rehypeStringify)\n .process(html);\n\n return String(result);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ox-content/vite-plugin",
3
- "version": "2.75.0",
3
+ "version": "2.76.0",
4
4
  "description": "Vite plugin for Ox Content - High-performance Markdown processing with Environment API",
5
5
  "keywords": [
6
6
  "environment-api",
@@ -63,7 +63,7 @@
63
63
  "shiki": "^4.0.2",
64
64
  "typescript": "^6.0.3",
65
65
  "unified": "^11.0.5",
66
- "@ox-content/napi": "2.75.0"
66
+ "@ox-content/napi": "2.76.0"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@playwright/test": "^1.60.0",