nuxt-og-image 6.6.0 → 6.7.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.
Files changed (35) hide show
  1. package/README.md +2 -0
  2. package/dist/chunks/tw4.cjs +7 -7
  3. package/dist/chunks/tw4.mjs +7 -7
  4. package/dist/chunks/uno.cjs +6 -6
  5. package/dist/chunks/uno.mjs +6 -6
  6. package/dist/cli.cjs +12 -5
  7. package/dist/cli.mjs +10 -3
  8. package/dist/module.cjs +5 -5
  9. package/dist/module.d.cts +5 -3
  10. package/dist/module.d.mts +5 -3
  11. package/dist/module.d.ts +5 -3
  12. package/dist/module.json +1 -1
  13. package/dist/module.mjs +5 -5
  14. package/dist/runtime/server/og-image/bindings/font-assets/cloudflare.d.ts +1 -1
  15. package/dist/runtime/server/og-image/bindings/font-assets/cloudflare.js +6 -1
  16. package/dist/runtime/server/og-image/bindings/font-assets/dev-prerender.js +13 -6
  17. package/dist/runtime/server/og-image/bindings/font-assets/external-url.d.ts +36 -0
  18. package/dist/runtime/server/og-image/bindings/font-assets/external-url.js +47 -0
  19. package/dist/runtime/server/og-image/bindings/font-assets/node.d.ts +1 -1
  20. package/dist/runtime/server/og-image/bindings/font-assets/node.js +14 -7
  21. package/dist/runtime/server/og-image/bindings/takumi/node-dev.js +7 -1
  22. package/dist/runtime/server/og-image/fonts.d.ts +4 -0
  23. package/dist/runtime/server/og-image/fonts.js +21 -5
  24. package/dist/runtime/server/og-image/satori/instances.d.ts +1 -2
  25. package/dist/runtime/server/og-image/takumi/renderer.js +61 -19
  26. package/dist/runtime/server/util/ssrf.d.ts +10 -0
  27. package/dist/runtime/server/util/ssrf.js +9 -1
  28. package/dist/runtime/types.d.ts +13 -0
  29. package/dist/shared/{nuxt-og-image.DdbTs-xp.mjs → nuxt-og-image.CPSxZhVO.mjs} +122 -496
  30. package/dist/shared/nuxt-og-image.DM30kcDz.mjs +660 -0
  31. package/dist/shared/nuxt-og-image.RI9DgjgV.cjs +689 -0
  32. package/dist/shared/{nuxt-og-image.CfTPCtaS.cjs → nuxt-og-image.yDJBWXBt.cjs} +181 -555
  33. package/package.json +43 -39
  34. package/dist/shared/nuxt-og-image.CJa2KCie.mjs +0 -189
  35. package/dist/shared/nuxt-og-image.CMYbz66P.cjs +0 -193
package/README.md CHANGED
@@ -47,6 +47,8 @@ npx nuxi@latest module add og-image
47
47
  > npx skilld add nuxt-og-image
48
48
  > ```
49
49
 
50
+ 💡 Built your OG images? Check how they render across Twitter, Facebook, LinkedIn and Slack with the free [Social Share Debugger](https://nuxtseo.com/tools/social-share-debugger), or monitor them site-wide with [Nuxt SEO Pro](https://nuxtseo.com/pro).
51
+
50
52
  ## Documentation
51
53
 
52
54
  [📖 Read the full documentation](https://nuxtseo.com/og-image/getting-started/installation) for more information.
@@ -4,7 +4,8 @@ const promises = require('node:fs/promises');
4
4
  const node_url = require('node:url');
5
5
  const exsolve = require('exsolve');
6
6
  const pathe = require('pathe');
7
- const module$1 = require('../shared/nuxt-og-image.CfTPCtaS.cjs');
7
+ const module$1 = require('../shared/nuxt-og-image.yDJBWXBt.cjs');
8
+ require('node:crypto');
8
9
  require('node:fs');
9
10
  require('@nuxt/kit');
10
11
  require('defu');
@@ -12,12 +13,14 @@ require('nuxt-site-config/kit');
12
13
  require('ohash');
13
14
  require('pkg-types');
14
15
  require('std-env');
15
- require('node:crypto');
16
16
  require('oxc-walker');
17
- require('nuxtseo-shared/kit');
17
+ require('../shared/nuxt-og-image.RI9DgjgV.cjs');
18
+ require('magicast');
19
+ require('magicast/helpers');
18
20
  require('../../dist/runtime/logger.js');
19
- require('chrome-launcher');
20
21
  require('nypm');
22
+ require('nuxtseo-shared/kit');
23
+ require('chrome-launcher');
21
24
  require('nuxtseo-shared/devtools');
22
25
  require('unstorage');
23
26
  require('unstorage/drivers/fs-lite');
@@ -30,9 +33,6 @@ require('@vue/compiler-sfc');
30
33
  require('ofetch');
31
34
  require('ultrahtml');
32
35
  require('../../dist/runtime/server/og-image/core/transforms/emojis/emoji-utils.js');
33
- require('../shared/nuxt-og-image.CMYbz66P.cjs');
34
- require('magicast');
35
- require('magicast/helpers');
36
36
 
37
37
  const RE_GRADIENT_STOP_PREFIX = /^(from|via|to)-/;
38
38
  const RE_COLOR_SHADE = /^(.+)-(\d+)$/;
@@ -2,7 +2,8 @@ import { readFile } from 'node:fs/promises';
2
2
  import { pathToFileURL } from 'node:url';
3
3
  import { resolveModulePath } from 'exsolve';
4
4
  import { dirname, join } from 'pathe';
5
- import { e as extractVariantBaseClasses, r as resolveVariantPrefixes, h as resolveExtractedVars, i as resolveVarsDeep, s as simplifyCss, p as postProcessStyles, l as loadLightningCss, a as extractCssVars, c as extractUniversalVars, d as extractPerClassVars, g as extractVarsFromCss, f as extractClassStyles } from '../shared/nuxt-og-image.DdbTs-xp.mjs';
5
+ import { e as extractVariantBaseClasses, r as resolveVariantPrefixes, h as resolveExtractedVars, i as resolveVarsDeep, s as simplifyCss, p as postProcessStyles, l as loadLightningCss, a as extractCssVars, c as extractUniversalVars, d as extractPerClassVars, g as extractVarsFromCss, f as extractClassStyles } from '../shared/nuxt-og-image.CPSxZhVO.mjs';
6
+ import 'node:crypto';
6
7
  import 'node:fs';
7
8
  import '@nuxt/kit';
8
9
  import 'defu';
@@ -10,12 +11,14 @@ import 'nuxt-site-config/kit';
10
11
  import 'ohash';
11
12
  import 'pkg-types';
12
13
  import 'std-env';
13
- import 'node:crypto';
14
14
  import 'oxc-walker';
15
- import 'nuxtseo-shared/kit';
15
+ import '../shared/nuxt-og-image.DM30kcDz.mjs';
16
+ import 'magicast';
17
+ import 'magicast/helpers';
16
18
  import '../../dist/runtime/logger.js';
17
- import 'chrome-launcher';
18
19
  import 'nypm';
20
+ import 'nuxtseo-shared/kit';
21
+ import 'chrome-launcher';
19
22
  import 'nuxtseo-shared/devtools';
20
23
  import 'unstorage';
21
24
  import 'unstorage/drivers/fs-lite';
@@ -28,9 +31,6 @@ import '@vue/compiler-sfc';
28
31
  import 'ofetch';
29
32
  import 'ultrahtml';
30
33
  import '../../dist/runtime/server/og-image/core/transforms/emojis/emoji-utils.js';
31
- import '../shared/nuxt-og-image.CJa2KCie.mjs';
32
- import 'magicast';
33
- import 'magicast/helpers';
34
34
 
35
35
  const RE_GRADIENT_STOP_PREFIX = /^(from|via|to)-/;
36
36
  const RE_COLOR_SHADE = /^(.+)-(\d+)$/;
@@ -3,7 +3,8 @@
3
3
  const promises = require('node:fs/promises');
4
4
  const defu = require('defu');
5
5
  const logger_js = require('../../dist/runtime/logger.js');
6
- const module$1 = require('../shared/nuxt-og-image.CfTPCtaS.cjs');
6
+ const module$1 = require('../shared/nuxt-og-image.yDJBWXBt.cjs');
7
+ require('node:crypto');
7
8
  require('node:fs');
8
9
  require('node:url');
9
10
  require('@nuxt/kit');
@@ -12,11 +13,13 @@ require('ohash');
12
13
  require('pathe');
13
14
  require('pkg-types');
14
15
  require('std-env');
15
- require('node:crypto');
16
16
  require('oxc-walker');
17
+ require('../shared/nuxt-og-image.RI9DgjgV.cjs');
18
+ require('magicast');
19
+ require('magicast/helpers');
20
+ require('nypm');
17
21
  require('nuxtseo-shared/kit');
18
22
  require('chrome-launcher');
19
- require('nypm');
20
23
  require('nuxtseo-shared/devtools');
21
24
  require('unstorage');
22
25
  require('unstorage/drivers/fs-lite');
@@ -30,9 +33,6 @@ require('ofetch');
30
33
  require('ultrahtml');
31
34
  require('../../dist/runtime/server/og-image/core/transforms/emojis/emoji-utils.js');
32
35
  require('exsolve');
33
- require('../shared/nuxt-og-image.CMYbz66P.cjs');
34
- require('magicast');
35
- require('magicast/helpers');
36
36
 
37
37
  const RE_SVG_DATA_URL = /url\("data:image\/svg\+xml[;,]([^"]+)"\)/;
38
38
  const RE_UTF8_PREFIX = /^utf8,/;
@@ -1,7 +1,8 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { defu } from 'defu';
3
3
  import { logger } from '../../dist/runtime/logger.js';
4
- import { e as extractVariantBaseClasses, s as simplifyCss, a as extractCssVars, b as extractPropertyInitialValues, c as extractUniversalVars, d as extractPerClassVars, f as extractClassStyles, p as postProcessStyles, r as resolveVariantPrefixes, g as extractVarsFromCss, h as resolveExtractedVars } from '../shared/nuxt-og-image.DdbTs-xp.mjs';
4
+ import { e as extractVariantBaseClasses, s as simplifyCss, a as extractCssVars, b as extractPropertyInitialValues, c as extractUniversalVars, d as extractPerClassVars, f as extractClassStyles, p as postProcessStyles, r as resolveVariantPrefixes, g as extractVarsFromCss, h as resolveExtractedVars } from '../shared/nuxt-og-image.CPSxZhVO.mjs';
5
+ import 'node:crypto';
5
6
  import 'node:fs';
6
7
  import 'node:url';
7
8
  import '@nuxt/kit';
@@ -10,11 +11,13 @@ import 'ohash';
10
11
  import 'pathe';
11
12
  import 'pkg-types';
12
13
  import 'std-env';
13
- import 'node:crypto';
14
14
  import 'oxc-walker';
15
+ import '../shared/nuxt-og-image.DM30kcDz.mjs';
16
+ import 'magicast';
17
+ import 'magicast/helpers';
18
+ import 'nypm';
15
19
  import 'nuxtseo-shared/kit';
16
20
  import 'chrome-launcher';
17
- import 'nypm';
18
21
  import 'nuxtseo-shared/devtools';
19
22
  import 'unstorage';
20
23
  import 'unstorage/drivers/fs-lite';
@@ -28,9 +31,6 @@ import 'ofetch';
28
31
  import 'ultrahtml';
29
32
  import '../../dist/runtime/server/og-image/core/transforms/emojis/emoji-utils.js';
30
33
  import 'exsolve';
31
- import '../shared/nuxt-og-image.CJa2KCie.mjs';
32
- import 'magicast';
33
- import 'magicast/helpers';
34
34
 
35
35
  const RE_SVG_DATA_URL = /url\("data:image\/svg\+xml[;,]([^"]+)"\)/;
36
36
  const RE_UTF8_PREFIX = /^utf8,/;
package/dist/cli.cjs CHANGED
@@ -10,10 +10,14 @@ const nypm = require('nypm');
10
10
  const oxcWalker = require('oxc-walker');
11
11
  const pathe = require('pathe');
12
12
  const ultrahtml = require('ultrahtml');
13
- const fonts = require('./shared/nuxt-og-image.CMYbz66P.cjs');
13
+ const dependencies = require('./shared/nuxt-og-image.RI9DgjgV.cjs');
14
14
  require('magicast');
15
15
  require('magicast/helpers');
16
16
  require('../dist/runtime/logger.js');
17
+ require('std-env');
18
+ require('defu');
19
+ require('nuxtseo-shared/kit');
20
+ require('chrome-launcher');
17
21
 
18
22
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
19
23
  function _interopNamespaceCompat(e) {
@@ -189,11 +193,14 @@ const RENDERERS = [
189
193
  }
190
194
  ];
191
195
  const EDGE_PRESETS = ["cloudflare", "cloudflare-pages", "cloudflare-module", "vercel-edge", "netlify-edge"];
192
- function getRendererDeps(renderer, isEdge) {
196
+ function getRendererDeps(renderer, isEdge, options = {}) {
193
197
  switch (renderer) {
194
198
  case "satori":
195
199
  return isEdge ? ["satori", "@resvg/resvg-js", "@resvg/resvg-wasm"] : ["satori", "@resvg/resvg-js"];
196
200
  case "takumi":
201
+ if (options.installSpec) {
202
+ return isEdge ? [dependencies.TAKUMI_CORE_INSTALL_SPEC, dependencies.TAKUMI_WASM_INSTALL_SPEC] : [dependencies.TAKUMI_CORE_INSTALL_SPEC];
203
+ }
197
204
  return isEdge ? ["@takumi-rs/core", "@takumi-rs/wasm"] : ["@takumi-rs/core"];
198
205
  case "browser":
199
206
  return isEdge ? [] : ["playwright-core"];
@@ -554,7 +561,7 @@ async function installRendererDeps(renderers, isEdge) {
554
561
  const pmName = pm?.name || "npm";
555
562
  const allDeps = [];
556
563
  for (const renderer of renderers) {
557
- allDeps.push(...getRendererDeps(renderer, isEdge));
564
+ allDeps.push(...getRendererDeps(renderer, isEdge, { installSpec: true }));
558
565
  }
559
566
  const uniqueDeps = [...new Set(allDeps)];
560
567
  if (uniqueDeps.length === 0)
@@ -706,7 +713,7 @@ async function runMigrate(args2) {
706
713
  if (dryRun) {
707
714
  p__namespace.log.info(`Would remove defaults.component: '${migrationCheck.defaultComponentName}'`);
708
715
  } else {
709
- const result = await fonts.migrateDefaultsComponent(cwd).catch((err) => {
716
+ const result = await dependencies.migrateDefaultsComponent(cwd).catch((err) => {
710
717
  p__namespace.log.warn(`Failed to migrate defaults.component: ${err.message}`);
711
718
  return { migrated: false, componentName: null, message: err.message };
712
719
  });
@@ -720,7 +727,7 @@ async function runMigrate(args2) {
720
727
  if (dryRun) {
721
728
  p__namespace.log.info("Would migrate ogImage.fonts to @nuxt/fonts config");
722
729
  } else {
723
- const result = await fonts.migrateFontsConfig(cwd).catch((err) => {
730
+ const result = await dependencies.migrateFontsConfig(cwd).catch((err) => {
724
731
  p__namespace.log.warn(`Failed to migrate fonts config: ${err.message}`);
725
732
  return { migrated: false, message: err.message };
726
733
  });
package/dist/cli.mjs CHANGED
@@ -8,10 +8,14 @@ import { detectPackageManager, removeDependency, addDependency } from 'nypm';
8
8
  import { parseAndWalk } from 'oxc-walker';
9
9
  import { dirname, resolve, join, relative, basename } from 'pathe';
10
10
  import { parse, walkSync, ELEMENT_NODE } from 'ultrahtml';
11
- import { m as migrateDefaultsComponent, a as migrateFontsConfig } from './shared/nuxt-og-image.CJa2KCie.mjs';
11
+ import { m as migrateDefaultsComponent, a as migrateFontsConfig, T as TAKUMI_CORE_INSTALL_SPEC, b as TAKUMI_WASM_INSTALL_SPEC } from './shared/nuxt-og-image.DM30kcDz.mjs';
12
12
  import 'magicast';
13
13
  import 'magicast/helpers';
14
14
  import '../dist/runtime/logger.js';
15
+ import 'std-env';
16
+ import 'defu';
17
+ import 'nuxtseo-shared/kit';
18
+ import 'chrome-launcher';
15
19
 
16
20
  const __dirname$1 = dirname(fileURLToPath(import.meta.url));
17
21
  const communityDir = resolve(__dirname$1, "runtime/app/components/Templates/Community");
@@ -172,11 +176,14 @@ const RENDERERS = [
172
176
  }
173
177
  ];
174
178
  const EDGE_PRESETS = ["cloudflare", "cloudflare-pages", "cloudflare-module", "vercel-edge", "netlify-edge"];
175
- function getRendererDeps(renderer, isEdge) {
179
+ function getRendererDeps(renderer, isEdge, options = {}) {
176
180
  switch (renderer) {
177
181
  case "satori":
178
182
  return isEdge ? ["satori", "@resvg/resvg-js", "@resvg/resvg-wasm"] : ["satori", "@resvg/resvg-js"];
179
183
  case "takumi":
184
+ if (options.installSpec) {
185
+ return isEdge ? [TAKUMI_CORE_INSTALL_SPEC, TAKUMI_WASM_INSTALL_SPEC] : [TAKUMI_CORE_INSTALL_SPEC];
186
+ }
180
187
  return isEdge ? ["@takumi-rs/core", "@takumi-rs/wasm"] : ["@takumi-rs/core"];
181
188
  case "browser":
182
189
  return isEdge ? [] : ["playwright-core"];
@@ -537,7 +544,7 @@ async function installRendererDeps(renderers, isEdge) {
537
544
  const pmName = pm?.name || "npm";
538
545
  const allDeps = [];
539
546
  for (const renderer of renderers) {
540
- allDeps.push(...getRendererDeps(renderer, isEdge));
547
+ allDeps.push(...getRendererDeps(renderer, isEdge, { installSpec: true }));
541
548
  }
542
549
  const uniqueDeps = [...new Set(allDeps)];
543
550
  if (uniqueDeps.length === 0)
package/dist/module.cjs CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ require('node:crypto');
3
4
  require('node:fs');
4
5
  require('node:fs/promises');
5
6
  require('node:url');
@@ -10,13 +11,11 @@ require('ohash');
10
11
  require('pathe');
11
12
  require('pkg-types');
12
13
  require('std-env');
13
- const module$1 = require('./shared/nuxt-og-image.CfTPCtaS.cjs');
14
+ const module$1 = require('./shared/nuxt-og-image.yDJBWXBt.cjs');
15
+ require('./shared/nuxt-og-image.RI9DgjgV.cjs');
14
16
  require('nuxtseo-shared/kit');
15
17
  require('../dist/runtime/logger.js');
16
- require('node:crypto');
17
18
  require('oxc-walker');
18
- require('chrome-launcher');
19
- require('nypm');
20
19
  require('nuxtseo-shared/devtools');
21
20
  require('unstorage');
22
21
  require('unstorage/drivers/fs-lite');
@@ -30,9 +29,10 @@ require('ofetch');
30
29
  require('ultrahtml');
31
30
  require('../dist/runtime/server/og-image/core/transforms/emojis/emoji-utils.js');
32
31
  require('exsolve');
33
- require('./shared/nuxt-og-image.CMYbz66P.cjs');
34
32
  require('magicast');
35
33
  require('magicast/helpers');
34
+ require('nypm');
35
+ require('chrome-launcher');
36
36
 
37
37
 
38
38
 
package/dist/module.d.cts CHANGED
@@ -186,12 +186,14 @@ interface ModuleOptions {
186
186
  * keyed hash signature and the handler rejects requests with missing or
187
187
  * invalid signatures.
188
188
  *
189
- * Must be a stable string across deployments. Generate one with:
189
+ * Leave unset to auto-generate a per-build secret (signing on by default).
190
+ * Set an explicit, stable string for rolling or multi-instance deploys, or
191
+ * `false` to disable signing entirely. Generate one with:
190
192
  * `npx nuxt-og-image generate-secret`
191
193
  *
192
- * Required when `strict` is enabled.
194
+ * Required (explicitly) when `strict` is enabled.
193
195
  */
194
- secret?: string;
196
+ secret?: string | false;
195
197
  /**
196
198
  * Enable strict security mode. When enabled:
197
199
  * - `secret` is required (URL signing)
package/dist/module.d.mts CHANGED
@@ -186,12 +186,14 @@ interface ModuleOptions {
186
186
  * keyed hash signature and the handler rejects requests with missing or
187
187
  * invalid signatures.
188
188
  *
189
- * Must be a stable string across deployments. Generate one with:
189
+ * Leave unset to auto-generate a per-build secret (signing on by default).
190
+ * Set an explicit, stable string for rolling or multi-instance deploys, or
191
+ * `false` to disable signing entirely. Generate one with:
190
192
  * `npx nuxt-og-image generate-secret`
191
193
  *
192
- * Required when `strict` is enabled.
194
+ * Required (explicitly) when `strict` is enabled.
193
195
  */
194
- secret?: string;
196
+ secret?: string | false;
195
197
  /**
196
198
  * Enable strict security mode. When enabled:
197
199
  * - `secret` is required (URL signing)
package/dist/module.d.ts CHANGED
@@ -186,12 +186,14 @@ interface ModuleOptions {
186
186
  * keyed hash signature and the handler rejects requests with missing or
187
187
  * invalid signatures.
188
188
  *
189
- * Must be a stable string across deployments. Generate one with:
189
+ * Leave unset to auto-generate a per-build secret (signing on by default).
190
+ * Set an explicit, stable string for rolling or multi-instance deploys, or
191
+ * `false` to disable signing entirely. Generate one with:
190
192
  * `npx nuxt-og-image generate-secret`
191
193
  *
192
- * Required when `strict` is enabled.
194
+ * Required (explicitly) when `strict` is enabled.
193
195
  */
194
- secret?: string;
196
+ secret?: string | false;
195
197
  /**
196
198
  * Enable strict security mode. When enabled:
197
199
  * - `secret` is required (URL signing)
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "nuxt": ">=3.16.0"
5
5
  },
6
6
  "configKey": "ogImage",
7
- "version": "6.6.0",
7
+ "version": "6.7.0",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import 'node:crypto';
1
2
  import 'node:fs';
2
3
  import 'node:fs/promises';
3
4
  import 'node:url';
@@ -8,13 +9,11 @@ import 'ohash';
8
9
  import 'pathe';
9
10
  import 'pkg-types';
10
11
  import 'std-env';
11
- export { m as default } from './shared/nuxt-og-image.DdbTs-xp.mjs';
12
+ export { m as default } from './shared/nuxt-og-image.CPSxZhVO.mjs';
13
+ import './shared/nuxt-og-image.DM30kcDz.mjs';
12
14
  import 'nuxtseo-shared/kit';
13
15
  import '../dist/runtime/logger.js';
14
- import 'node:crypto';
15
16
  import 'oxc-walker';
16
- import 'chrome-launcher';
17
- import 'nypm';
18
17
  import 'nuxtseo-shared/devtools';
19
18
  import 'unstorage';
20
19
  import 'unstorage/drivers/fs-lite';
@@ -28,6 +27,7 @@ import 'ofetch';
28
27
  import 'ultrahtml';
29
28
  import '../dist/runtime/server/og-image/core/transforms/emojis/emoji-utils.js';
30
29
  import 'exsolve';
31
- import './shared/nuxt-og-image.CJa2KCie.mjs';
32
30
  import 'magicast';
33
31
  import 'magicast/helpers';
32
+ import 'nypm';
33
+ import 'chrome-launcher';
@@ -1,3 +1,3 @@
1
1
  import type { H3Event } from 'h3';
2
2
  import type { FontConfig } from '../../../../types.js';
3
- export declare function resolve(event: H3Event, font: FontConfig): Promise<Buffer<ArrayBuffer>>;
3
+ export declare function resolve(event: H3Event, font: FontConfig): Promise<Buffer<ArrayBufferLike>>;
@@ -1,14 +1,19 @@
1
1
  import { useRuntimeConfig } from "nitropack/runtime";
2
2
  import { withBase } from "ufo";
3
+ import { getSiteConfig } from "#site-config/server/composables";
3
4
  import { getCloudflareAssets } from "../../../util/cloudflareAssets.js";
4
5
  import { fetchLocalAsset } from "../../../util/fetchLocalAsset.js";
5
6
  import { getFetchTimeout } from "../../../util/fetchTimeout.js";
6
7
  import { useOgImageRuntimeConfig } from "../../../utils.js";
8
+ import { fetchSpecialFontUrl, isDataFontUrl, isExternalFontUrl } from "./external-url.js";
7
9
  export async function resolve(event, font) {
8
10
  const path = font.src || font.localPath;
11
+ const runtimeConfig = useOgImageRuntimeConfig();
12
+ const timeout = getFetchTimeout(runtimeConfig);
9
13
  const { app } = useRuntimeConfig();
10
14
  const fullPath = withBase(path, app.baseURL);
11
- const timeout = getFetchTimeout(useOgImageRuntimeConfig());
15
+ if (path && (isDataFontUrl(path) || isExternalFontUrl(path)))
16
+ return fetchSpecialFontUrl(path, getSiteConfig(event).url, timeout);
12
17
  const ab = await fetchLocalAsset(event, fullPath, { fetchTimeout: timeout });
13
18
  if (ab)
14
19
  return Buffer.from(ab);
@@ -4,8 +4,11 @@ import { useRuntimeConfig } from "nitropack/runtime";
4
4
  import { join } from "pathe";
5
5
  import { withBase } from "ufo";
6
6
  import { buildDir, rootDir } from "#og-image-virtual/build-dir.mjs";
7
+ import { getSiteConfig } from "#site-config/server/composables";
7
8
  import { getFetchTimeout } from "../../../util/fetchTimeout.js";
9
+ import { fetchWithRedirectValidation } from "../../../util/ssrf.js";
8
10
  import { useOgImageRuntimeConfig } from "../../../utils.js";
11
+ import { fetchSpecialFontUrl, isDataFontUrl, isExternalFontUrl } from "./external-url.js";
9
12
  let fontUrlMapping;
10
13
  async function loadFontUrlMapping() {
11
14
  if (fontUrlMapping)
@@ -16,12 +19,15 @@ async function loadFontUrlMapping() {
16
19
  }
17
20
  export async function resolve(event, font) {
18
21
  const path = font.src || font.localPath;
19
- const timeout = getFetchTimeout(useOgImageRuntimeConfig());
22
+ const runtimeConfig = useOgImageRuntimeConfig();
23
+ const timeout = getFetchTimeout(runtimeConfig);
20
24
  if (font.absolutePath) {
21
25
  const data = await readFile(font.absolutePath).catch(() => null);
22
26
  if (data?.length)
23
27
  return data;
24
28
  }
29
+ if (path && (isDataFontUrl(path) || isExternalFontUrl(path)))
30
+ return fetchSpecialFontUrl(path, getSiteConfig(event).url, timeout);
25
31
  if (import.meta.prerender) {
26
32
  if (path.startsWith("/_og-static-fonts/")) {
27
33
  const filename2 = path.slice("/_og-static-fonts/".length);
@@ -72,14 +78,15 @@ export async function resolve(event, font) {
72
78
  if (import.meta.dev) {
73
79
  const reqUrl = getRequestURL(event);
74
80
  const origin = `${reqUrl.protocol}//${reqUrl.host}`;
75
- const url = new URL(withBase(path, app.baseURL), origin).href;
76
- const res = await fetch(url, { signal: AbortSignal.timeout(timeout) }).catch(() => null);
77
- if (res?.ok) {
78
- return Buffer.from(await res.arrayBuffer());
81
+ const target = new URL(withBase(path, app.baseURL), origin);
82
+ const ab = await fetchWithRedirectValidation(target.href, { timeout, trustedHost: target.host }).catch(() => null);
83
+ if (ab) {
84
+ return Buffer.from(ab);
79
85
  }
80
86
  }
81
87
  const fullPath = withBase(path, app.baseURL);
82
- const arrayBuffer = await event.$fetch(fullPath, {
88
+ const fetchArrayBuffer = event.$fetch;
89
+ const arrayBuffer = await fetchArrayBuffer(fullPath, {
83
90
  responseType: "arrayBuffer",
84
91
  timeout
85
92
  });
@@ -0,0 +1,36 @@
1
+ /** True for an inline `data:` font URI (no network — safe to decode directly). */
2
+ export declare function isDataFontUrl(path: string): boolean;
3
+ /**
4
+ * True when a font path carries its own authority (`//host`, `http://host`,
5
+ * `\\host`) or a non-http scheme — i.e. it would not resolve same-origin.
6
+ *
7
+ * Classification resolves the path against a sentinel origin through the WHATWG
8
+ * URL parser, so it sees exactly what `fetch` will: leading C0 controls/spaces,
9
+ * embedded tab/CR/LF, and backslash folding are all normalized first. This
10
+ * closes the bypass class where " //127.0.0.1" or " //169.254.169.254" looks
11
+ * relative to a naive scheme check yet resolves cross-origin (GHSA-q8hw-4fvp-9rwv).
12
+ * Check `isDataFontUrl` first — `data:` is non-http and would report true here.
13
+ */
14
+ export declare function isExternalFontUrl(path: string): boolean;
15
+ /**
16
+ * Resolve an authority-bearing font URL to the href to fetch, but only when it
17
+ * is same-origin with the configured site URL. Returns null otherwise.
18
+ *
19
+ * Runtime external font URLs are unsupported — `@nuxt/fonts` is the only
20
+ * supported way to load custom fonts, and it serves them same-origin. The single
21
+ * allowed exception is the site's own origin, gated on an explicitly configured
22
+ * site URL. Resolving against that origin also collapses canonicalization
23
+ * bypasses (a crafted " //127.0.0.1" resolves cross-origin → rejected).
24
+ */
25
+ export declare function resolveSameOriginFontUrl(path: string, siteUrl: string | undefined): string | null;
26
+ /**
27
+ * Fetch a `data:` or authority-bearing font URL. `data:` is decoded directly
28
+ * (no network). An external URL is fetched only when same-origin with the site
29
+ * URL, and always through the SSRF guard (scheme allowlist, private-network
30
+ * block, per-hop redirect re-validation) so the site's own host can't be used
31
+ * to relay into its internal network via an open redirect.
32
+ *
33
+ * Throws on an unsupported/blocked/unreachable URL so the caller falls back to a
34
+ * bundled font; blocked targets stay indistinguishable from missing fonts.
35
+ */
36
+ export declare function fetchSpecialFontUrl(path: string, siteUrl: string | undefined, timeout: number): Promise<Buffer>;
@@ -0,0 +1,47 @@
1
+ import { fetchWithRedirectValidation } from "../../../util/ssrf.js";
2
+ const SENTINEL_ORIGIN = "http://font-asset.invalid/";
3
+ const SENTINEL_HOST = "font-asset.invalid";
4
+ export function isDataFontUrl(path) {
5
+ return path.trimStart().toLowerCase().startsWith("data:");
6
+ }
7
+ export function isExternalFontUrl(path) {
8
+ let resolved;
9
+ try {
10
+ resolved = new URL(path, SENTINEL_ORIGIN);
11
+ } catch {
12
+ return true;
13
+ }
14
+ return resolved.protocol !== "http:" || resolved.host !== SENTINEL_HOST;
15
+ }
16
+ export function resolveSameOriginFontUrl(path, siteUrl) {
17
+ if (!siteUrl)
18
+ return null;
19
+ let siteOrigin;
20
+ try {
21
+ siteOrigin = new URL(siteUrl).origin;
22
+ } catch {
23
+ return null;
24
+ }
25
+ let target;
26
+ try {
27
+ target = new URL(path, siteOrigin);
28
+ } catch {
29
+ return null;
30
+ }
31
+ return target.origin === siteOrigin ? target.href : null;
32
+ }
33
+ export async function fetchSpecialFontUrl(path, siteUrl, timeout) {
34
+ if (isDataFontUrl(path)) {
35
+ const res = await fetch(path.trimStart(), { signal: AbortSignal.timeout(timeout) }).catch(() => null);
36
+ if (res?.ok)
37
+ return Buffer.from(await res.arrayBuffer());
38
+ throw new Error("[Nuxt OG Image] Invalid data: font URL.");
39
+ }
40
+ const href = resolveSameOriginFontUrl(path, siteUrl);
41
+ if (!href)
42
+ throw new Error("[Nuxt OG Image] External font URLs are not supported. Load custom fonts via @nuxt/fonts.");
43
+ const ab = await fetchWithRedirectValidation(href, { timeout });
44
+ if (!ab)
45
+ throw new Error("[Nuxt OG Image] Font URL blocked or unreachable.");
46
+ return Buffer.from(ab);
47
+ }
@@ -1,3 +1,3 @@
1
1
  import type { H3Event } from 'h3';
2
2
  import type { FontConfig } from '../../../../types.js';
3
- export declare function resolve(event: H3Event, font: FontConfig): Promise<Buffer<ArrayBuffer>>;
3
+ export declare function resolve(event: H3Event, font: FontConfig): Promise<Buffer<ArrayBufferLike>>;
@@ -1,19 +1,26 @@
1
1
  import { useRuntimeConfig } from "nitropack/runtime";
2
2
  import { withBase } from "ufo";
3
- import { getNitroOrigin } from "#site-config/server/composables";
3
+ import { getNitroOrigin, getSiteConfig } from "#site-config/server/composables";
4
4
  import { getFetchTimeout } from "../../../util/fetchTimeout.js";
5
+ import { fetchWithRedirectValidation } from "../../../util/ssrf.js";
5
6
  import { useOgImageRuntimeConfig } from "../../../utils.js";
7
+ import { fetchSpecialFontUrl, isDataFontUrl, isExternalFontUrl } from "./external-url.js";
6
8
  export async function resolve(event, font) {
7
9
  const path = font.src || font.localPath;
10
+ const runtimeConfig = useOgImageRuntimeConfig();
11
+ const timeout = getFetchTimeout(runtimeConfig);
8
12
  const { app } = useRuntimeConfig();
9
- const fullPath = withBase(path, app.baseURL);
10
13
  const origin = getNitroOrigin(event);
11
- const timeout = getFetchTimeout(useOgImageRuntimeConfig());
12
- const res = await fetch(new URL(fullPath, origin).href, { signal: AbortSignal.timeout(timeout) }).catch(() => null);
13
- if (res?.ok) {
14
- return Buffer.from(await res.arrayBuffer());
14
+ const fullPath = withBase(path, app.baseURL);
15
+ if (path && (isDataFontUrl(path) || isExternalFontUrl(path)))
16
+ return fetchSpecialFontUrl(path, getSiteConfig(event).url, timeout);
17
+ const target = new URL(fullPath, origin);
18
+ const ab = await fetchWithRedirectValidation(target.href, { timeout, trustedHost: target.host }).catch(() => null);
19
+ if (ab) {
20
+ return Buffer.from(ab);
15
21
  }
16
- const arrayBuffer = await event.$fetch(fullPath, {
22
+ const fetchArrayBuffer = event.$fetch;
23
+ const arrayBuffer = await fetchArrayBuffer(fullPath, {
17
24
  responseType: "arrayBuffer",
18
25
  timeout
19
26
  });
@@ -17,7 +17,10 @@ parentPort.on('message', async ({ id, type, newFonts, nodes, options }) => {
17
17
  const fontWarnings = []
18
18
  for (const font of (newFonts || [])) {
19
19
  try {
20
- await renderer.loadFont({
20
+ const registerFont = renderer.registerFont || renderer.loadFont
21
+ if (typeof registerFont !== 'function')
22
+ throw new Error('renderer does not expose loadFont/registerFont')
23
+ await registerFont.call(renderer, {
21
24
  name: font.name,
22
25
  data: font.data,
23
26
  weight: font.weight,
@@ -127,6 +130,9 @@ class RendererWorkerProxy {
127
130
  this.allFonts.push(font);
128
131
  this.pendingFonts.push(font);
129
132
  }
133
+ registerFont(font) {
134
+ this.loadFont(font);
135
+ }
130
136
  render(nodes, options) {
131
137
  ensureWorker();
132
138
  let fontsToSend;
@@ -42,6 +42,10 @@ export declare function loadAllFonts(event: H3Event, options: LoadFontsOptions):
42
42
  /**
43
43
  * Load additional fonts specified via defineOgImage({ fonts: [...] }).
44
44
  * Only object format ({ name, weight, path }) with a path is supported.
45
+ *
46
+ * Field values arrive from the attacker-controlled `fonts` URL param, so each is
47
+ * parsed at this boundary (require string name/path, coerce weight) and the
48
+ * array is bounded before any font is fetched.
45
49
  */
46
50
  export declare function loadDefinedFonts(event: OgImageRenderEventContext, fontDefs: any[]): Promise<RuntimeFontConfig[]>;
47
51
  export interface LoadFontsForRendererOptions extends LoadFontsOptions {