nuxt-og-image 6.4.7 → 6.4.9
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.
- package/dist/chunks/tw4.cjs +1 -1
- package/dist/chunks/tw4.mjs +1 -1
- package/dist/chunks/uno.cjs +1 -1
- package/dist/chunks/uno.mjs +1 -1
- package/dist/devtools/200.html +1 -1
- package/dist/devtools/404.html +1 -1
- package/dist/devtools/_nuxt/Axr0GNgb.js +23 -0
- package/dist/devtools/_nuxt/BFnUFv7a.js +6 -0
- package/dist/devtools/_nuxt/BLdiDWsd.js +3 -0
- package/dist/devtools/_nuxt/BPuj2Ioi.js +1 -0
- package/dist/devtools/_nuxt/BhUO5EEy.js +4 -0
- package/dist/devtools/_nuxt/{C2qZkcnS.js → Bu77Nt5h.js} +1 -1
- package/dist/devtools/_nuxt/CbxDj8dt.js +2 -0
- package/dist/devtools/_nuxt/CeQOmnUG.js +1 -0
- package/dist/devtools/_nuxt/Ceo32Wod.js +1 -0
- package/dist/devtools/_nuxt/{B75jVnAo.js → D-SbM8XJ.js} +1 -1
- package/dist/devtools/_nuxt/DWbsyffp.js +152 -0
- package/dist/devtools/_nuxt/Dd8dEdTA.js +3 -0
- package/dist/devtools/_nuxt/DevtoolsSection.BsOFzz12.css +1 -0
- package/dist/devtools/_nuxt/DevtoolsSnippet.DTkjQmsJ.css +1 -0
- package/dist/devtools/_nuxt/{IFrameLoader.HTWy4e5S.css → IFrameLoader.Bd09J70F.css} +1 -1
- package/dist/devtools/_nuxt/builds/latest.json +1 -1
- package/dist/devtools/_nuxt/builds/meta/df7e4f86-26df-4aa8-839c-9d7fb31dd16b.json +1 -0
- package/dist/devtools/_nuxt/entry.D7nBa7-d.css +2 -0
- package/dist/devtools/_nuxt/{D5adIWRn.js → oq4DzcWM.js} +1 -1
- package/dist/devtools/_nuxt/{pages.DNfeVIDd.css → pages.D6uci9iO.css} +1 -1
- package/dist/devtools/_nuxt/renderer-select.Pd5oWk7V.css +1 -0
- package/dist/devtools/_nuxt/{templates.C7e57cTp.css → templates.BHO7w0UG.css} +1 -1
- package/dist/devtools/debug/index.html +1 -1
- package/dist/devtools/docs/index.html +1 -1
- package/dist/devtools/index.html +1 -1
- package/dist/devtools/templates/index.html +1 -1
- package/dist/module.cjs +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/app/client-utils.js +1 -1
- package/dist/runtime/app/components/Templates/Community/SimpleBlog.satori.vue +1 -1
- package/dist/runtime/app/utils/plugins.js +4 -4
- package/dist/runtime/app/utils.js +1 -1
- package/dist/runtime/server/og-image/bindings/browser/chrome-launcher.js +2 -1
- package/dist/runtime/server/og-image/bindings/browser/cloudflare.js +4 -2
- package/dist/runtime/server/og-image/bindings/browser/on-demand.js +2 -1
- package/dist/runtime/server/og-image/bindings/browser/playwright.js +2 -1
- package/dist/runtime/server/og-image/bindings/font-assets/dev-prerender.js +1 -1
- package/dist/runtime/server/og-image/bindings/font-assets/node.js +1 -1
- package/dist/runtime/server/og-image/browser/renderer.js +1 -1
- package/dist/runtime/server/og-image/browser/screenshot.js +2 -2
- package/dist/runtime/server/og-image/cache/buildCache.js +1 -1
- package/dist/runtime/server/og-image/context.js +12 -5
- package/dist/runtime/server/og-image/core/plugins/imageSrc.js +30 -59
- package/dist/runtime/server/og-image/core/transforms/emojis/fetch.js +1 -1
- package/dist/runtime/server/og-image/core/vnodes.js +3 -2
- package/dist/runtime/server/og-image/devtools.js +1 -1
- package/dist/runtime/server/og-image/font-source.d.ts +20 -0
- package/dist/runtime/server/og-image/font-source.js +20 -0
- package/dist/runtime/server/og-image/fonts.d.ts +8 -1
- package/dist/runtime/server/og-image/fonts.js +22 -32
- package/dist/runtime/server/og-image/satori/plugins/emojis.js +1 -1
- package/dist/runtime/server/og-image/satori/renderer.js +9 -3
- package/dist/runtime/server/og-image/takumi/nodes.js +1 -1
- package/dist/runtime/server/og-image/takumi/renderer.js +76 -24
- package/dist/runtime/server/og-image/templates/html.js +3 -2
- package/dist/runtime/server/plugins/prerender.js +2 -2
- package/dist/runtime/server/routes/debug.json.js +2 -2
- package/dist/runtime/server/routes/resolve.js +2 -2
- package/dist/runtime/server/util/eventHandlers.js +3 -2
- package/dist/runtime/server/util/fetchLocalAsset.js +1 -1
- package/dist/runtime/server/util/kit.d.ts +1 -1
- package/dist/runtime/server/util/kit.js +5 -2
- package/dist/runtime/server/util/options.js +1 -1
- package/dist/runtime/server/util/ssrf.d.ts +27 -0
- package/dist/runtime/server/util/ssrf.js +165 -0
- package/dist/runtime/server/util/withTimeout.d.ts +1 -0
- package/dist/runtime/server/util/withTimeout.js +12 -0
- package/dist/runtime/server/utils.js +6 -2
- package/dist/shared/{nuxt-og-image.Cr3WHMk1.mjs → nuxt-og-image.CW3PwVsO.mjs} +7 -2
- package/dist/shared/{nuxt-og-image.TJuh6pW5.cjs → nuxt-og-image.DnwTVrnM.cjs} +8 -3
- package/package.json +18 -17
- package/types/virtual.d.ts +1 -1
- package/dist/devtools/_nuxt/671cdwR5.js +0 -1
- package/dist/devtools/_nuxt/8kkJjsM6.js +0 -2
- package/dist/devtools/_nuxt/BJ9iyiD_.js +0 -3
- package/dist/devtools/_nuxt/BcGh1UWn.js +0 -23
- package/dist/devtools/_nuxt/CHmOAOu6.js +0 -3
- package/dist/devtools/_nuxt/Ci8ZvNRB.js +0 -1
- package/dist/devtools/_nuxt/DVtbGlya.js +0 -152
- package/dist/devtools/_nuxt/DbOyl855.js +0 -1
- package/dist/devtools/_nuxt/DevtoolsSection.DTZAmexP.css +0 -1
- package/dist/devtools/_nuxt/DevtoolsSnippet.Cd7XR-3f.css +0 -1
- package/dist/devtools/_nuxt/DrqfEi04.js +0 -4
- package/dist/devtools/_nuxt/builds/meta/d8fa30f4-68dd-4f57-a1c2-1c3a852f7ee7.json +0 -1
- package/dist/devtools/_nuxt/cIRSrPLM.js +0 -6
- package/dist/devtools/_nuxt/entry.zgUFdiSv.css +0 -2
- package/dist/devtools/_nuxt/renderer-select.elTTxv30.css +0 -1
|
@@ -3,6 +3,8 @@ import { withBase } from "ufo";
|
|
|
3
3
|
import { logger } from "../../../logger.js";
|
|
4
4
|
import { fetchLocalAsset } from "../../util/fetchLocalAsset.js";
|
|
5
5
|
import { getFetchTimeout } from "../../util/fetchTimeout.js";
|
|
6
|
+
import { fetchWithRedirectValidation, isBlockedUrl } from "../../util/ssrf.js";
|
|
7
|
+
import { withTimeout } from "../../util/withTimeout.js";
|
|
6
8
|
import { buildSubsetFamilyChain, extractCodepoints, getDefaultFontFamily, loadFontsForRenderer, resolveSubsetChain } from "../fonts.js";
|
|
7
9
|
import { getExtractResourceUrls, getTakumi } from "./instances.js";
|
|
8
10
|
import { createTakumiNodes } from "./nodes.js";
|
|
@@ -20,10 +22,45 @@ async function getTakumiState(event) {
|
|
|
20
22
|
};
|
|
21
23
|
return nitro._takumiState;
|
|
22
24
|
}
|
|
23
|
-
function withTakumiLock(state, fn) {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
function withTakumiLock(state, timeoutMs, fn, onLockTimeout) {
|
|
26
|
+
const guarded = async () => {
|
|
27
|
+
let timer;
|
|
28
|
+
try {
|
|
29
|
+
return await Promise.race([
|
|
30
|
+
fn(),
|
|
31
|
+
new Promise((_, reject) => {
|
|
32
|
+
timer = setTimeout(
|
|
33
|
+
() => reject(new Error(`takumi render timed out after ${timeoutMs}ms (lock-held)`)),
|
|
34
|
+
timeoutMs
|
|
35
|
+
);
|
|
36
|
+
})
|
|
37
|
+
]);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
try {
|
|
40
|
+
const Renderer = await getTakumi();
|
|
41
|
+
state.renderer = new Renderer();
|
|
42
|
+
state.loadedFontKeys.clear();
|
|
43
|
+
state.loadedFamilies.clear();
|
|
44
|
+
} catch (resetErr) {
|
|
45
|
+
logger.warn(`failed to reset takumi renderer after lock timeout: ${resetErr?.message || resetErr}`);
|
|
46
|
+
}
|
|
47
|
+
throw err;
|
|
48
|
+
} finally {
|
|
49
|
+
clearTimeout(timer);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const work = state.lock.then(guarded, guarded);
|
|
53
|
+
state.lock = work.catch(() => void 0);
|
|
54
|
+
let acquireTimer;
|
|
55
|
+
return Promise.race([
|
|
56
|
+
work,
|
|
57
|
+
new Promise((_, reject) => {
|
|
58
|
+
acquireTimer = setTimeout(() => {
|
|
59
|
+
onLockTimeout?.();
|
|
60
|
+
reject(new Error(`takumi lock acquire timed out after ${timeoutMs}ms`));
|
|
61
|
+
}, timeoutMs);
|
|
62
|
+
})
|
|
63
|
+
]).finally(() => clearTimeout(acquireTimer));
|
|
27
64
|
}
|
|
28
65
|
function dedupeFontsByBinary(fonts) {
|
|
29
66
|
const byBinary = /* @__PURE__ */ new Map();
|
|
@@ -116,11 +153,16 @@ async function createImage(event, format) {
|
|
|
116
153
|
const { fontFamilyOverride, defaultFont } = getDefaultFontFamily(options);
|
|
117
154
|
const nodes = await createTakumiNodes(event);
|
|
118
155
|
const codepoints = extractCodepoints(nodes);
|
|
119
|
-
const fonts = await timings.measure("font-load", () => loadFontsForRenderer(event, { supportedFormats: /* @__PURE__ */ new Set(["ttf", "woff2"]), component: options.component, fontFamilyOverride: fontFamilyOverride || defaultFont, codepoints }));
|
|
120
|
-
|
|
156
|
+
const fonts = await timings.measure("font-load", () => loadFontsForRenderer(event, { supportedFormats: /* @__PURE__ */ new Set(["ttf", "woff2"]), preferStatic: true, component: options.component, fontFamilyOverride: fontFamilyOverride || defaultFont, codepoints }));
|
|
157
|
+
const hookTimeout = event.runtimeConfig.security?.renderTimeout ?? 15e3;
|
|
158
|
+
await withTimeout(
|
|
159
|
+
event._nitro.hooks.callHook("nuxt-og-image:takumi:nodes", nodes, event),
|
|
160
|
+
hookTimeout,
|
|
161
|
+
"nuxt-og-image:takumi:nodes hook"
|
|
162
|
+
);
|
|
121
163
|
const subsetChains = buildSubsetFamilyChain(fonts);
|
|
122
|
-
const state = await getTakumiState(event);
|
|
123
|
-
const extractResourceUrls = await getExtractResourceUrls();
|
|
164
|
+
const state = await timings.measure("takumi-init", () => getTakumiState(event));
|
|
165
|
+
const extractResourceUrls = await timings.measure("takumi-extract-init", () => getExtractResourceUrls());
|
|
124
166
|
const resourceUrls = await extractResourceUrls(nodes);
|
|
125
167
|
const baseURL = event.runtimeConfig.app.baseURL;
|
|
126
168
|
const fetchedResources = [];
|
|
@@ -136,13 +178,18 @@ async function createImage(event, format) {
|
|
|
136
178
|
headers,
|
|
137
179
|
includeExternalFallback: true
|
|
138
180
|
});
|
|
139
|
-
} else {
|
|
181
|
+
} else if (import.meta.dev) {
|
|
140
182
|
data = await $fetch(src, {
|
|
141
183
|
responseType: "arrayBuffer",
|
|
142
184
|
signal: AbortSignal.timeout(fetchTimeout),
|
|
143
185
|
timeout: fetchTimeout,
|
|
144
186
|
headers
|
|
145
187
|
}).catch(() => void 0);
|
|
188
|
+
} else if (!isBlockedUrl(src)) {
|
|
189
|
+
data = await fetchWithRedirectValidation(src, {
|
|
190
|
+
timeout: fetchTimeout,
|
|
191
|
+
headers
|
|
192
|
+
}) ?? void 0;
|
|
146
193
|
}
|
|
147
194
|
if (data)
|
|
148
195
|
fetchedResources.push({ src, data: new Uint8Array(data) });
|
|
@@ -158,21 +205,26 @@ async function createImage(event, format) {
|
|
|
158
205
|
fetchedResources,
|
|
159
206
|
devicePixelRatio: dpr
|
|
160
207
|
});
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
208
|
+
const lockTimeout = event.runtimeConfig.security?.renderTimeout ?? 15e3;
|
|
209
|
+
const endLockWait = timings.start("lock-wait");
|
|
210
|
+
return await withTakumiLock(state, lockTimeout, () => {
|
|
211
|
+
endLockWait();
|
|
212
|
+
return timings.measure("render-takumi", async () => {
|
|
213
|
+
await loadFontsIntoRenderer(state, fonts);
|
|
214
|
+
const rootStyle = nodes.style ?? {};
|
|
215
|
+
if (fontFamilyOverride) {
|
|
216
|
+
const chain = subsetChains.get(fontFamilyOverride);
|
|
217
|
+
if (chain) {
|
|
218
|
+
rootStyle.fontFamily = chain.map((f) => `"${f}"`).join(", ");
|
|
219
|
+
} else if (state.loadedFamilies.has(fontFamilyOverride)) {
|
|
220
|
+
rootStyle.fontFamily = fontFamilyOverride;
|
|
221
|
+
}
|
|
170
222
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
})
|
|
223
|
+
nodes.style = rootStyle;
|
|
224
|
+
rewriteFontFamilies(nodes, state.loadedFamilies, subsetChains);
|
|
225
|
+
return state.renderer.render(nodes, renderOptions);
|
|
226
|
+
});
|
|
227
|
+
}, endLockWait);
|
|
176
228
|
}
|
|
177
229
|
const TakumiRenderer = {
|
|
178
230
|
name: "takumi",
|
|
@@ -191,7 +243,7 @@ const TakumiRenderer = {
|
|
|
191
243
|
async debug(e) {
|
|
192
244
|
const [vnodes, fonts] = await Promise.all([
|
|
193
245
|
createTakumiNodes(e),
|
|
194
|
-
loadFontsForRenderer(e, { supportedFormats: /* @__PURE__ */ new Set(["ttf", "woff2"]), component: e.options.component })
|
|
246
|
+
loadFontsForRenderer(e, { supportedFormats: /* @__PURE__ */ new Set(["ttf", "woff2"]), preferStatic: true, component: e.options.component })
|
|
195
247
|
]);
|
|
196
248
|
return {
|
|
197
249
|
vnodes,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import resolvedFonts from "#og-image/fonts";
|
|
2
1
|
import { createHead, renderSSRHead } from "@unhead/vue/server";
|
|
3
2
|
import { createError } from "h3";
|
|
3
|
+
import resolvedFonts from "#og-image/fonts";
|
|
4
4
|
import { fetchIsland } from "../../util/kit.js";
|
|
5
5
|
import { applyEmojis } from "../core/transforms/emojis/index.js";
|
|
6
6
|
const RE_SCRIPT_TAG = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi;
|
|
@@ -13,7 +13,8 @@ export async function html(ctx) {
|
|
|
13
13
|
statusMessage: `[Nuxt OG Image] Rendering an invalid component. Received options: ${JSON.stringify(options)}.`
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
|
-
const
|
|
16
|
+
const islandTimeout = ctx.runtimeConfig.security?.renderTimeout ?? 15e3;
|
|
17
|
+
const island = await fetchIsland(ctx.e, ctx.options.component, typeof ctx.options.props !== "undefined" ? ctx.options.props : ctx.options, islandTimeout);
|
|
17
18
|
const head = createHead();
|
|
18
19
|
head.push(island.head);
|
|
19
20
|
let defaultFontFamily = "sans-serif";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { prerenderOptionsCache } from "#og-image-cache";
|
|
2
|
-
import { createSitePathResolver } from "#site-config/server/composables/utils";
|
|
3
1
|
import { parse } from "devalue";
|
|
4
2
|
import { appendResponseHeader } from "h3";
|
|
5
3
|
import { defineNitroPlugin } from "nitropack/runtime";
|
|
6
4
|
import { parseURL } from "ufo";
|
|
5
|
+
import { prerenderOptionsCache } from "#og-image-cache";
|
|
6
|
+
import { createSitePathResolver } from "#site-config/server/composables/utils";
|
|
7
7
|
import { isInternalRoute } from "../../shared.js";
|
|
8
8
|
import { resolvePathCacheKey } from "../og-image/context.js";
|
|
9
9
|
import { createNitroRouteRuleMatcher } from "../util/kit.js";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { defineEventHandler, setHeader } from "h3";
|
|
1
2
|
import { componentNames } from "#og-image-virtual/component-names.mjs";
|
|
2
3
|
import compatibility from "#og-image/compatibility";
|
|
3
4
|
import { fontRequirements } from "#og-image/font-requirements";
|
|
@@ -5,11 +6,10 @@ import resolvedFonts from "#og-image/fonts";
|
|
|
5
6
|
import availableFonts from "#og-image/fonts-available";
|
|
6
7
|
import { getNitroOrigin } from "#site-config/server/composables";
|
|
7
8
|
import { getSiteConfig } from "#site-config/server/composables/getSiteConfig";
|
|
8
|
-
import { defineEventHandler, setHeader } from "h3";
|
|
9
9
|
import { useOgImageRuntimeConfig } from "../utils.js";
|
|
10
10
|
export default defineEventHandler(async (e) => {
|
|
11
11
|
setHeader(e, "Content-Type", "application/json");
|
|
12
|
-
const runtimeConfig = useOgImageRuntimeConfig();
|
|
12
|
+
const runtimeConfig = useOgImageRuntimeConfig(e);
|
|
13
13
|
return {
|
|
14
14
|
siteConfigUrl: getSiteConfig(e).url,
|
|
15
15
|
origin: getNitroOrigin(e),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { getSiteConfig } from "#site-config/server/composables/getSiteConfig";
|
|
2
1
|
import { createError, defineEventHandler, getQuery, getRequestHost, sendRedirect } from "h3";
|
|
3
2
|
import { parseURL, withLeadingSlash, withQuery } from "ufo";
|
|
3
|
+
import { getSiteConfig } from "#site-config/server/composables/getSiteConfig";
|
|
4
4
|
import { isInternalRoute } from "../../shared.js";
|
|
5
5
|
import { useOgImageRuntimeConfig } from "../utils.js";
|
|
6
6
|
const RE_META_TAG = /<meta\b[^>]*>/gi;
|
|
@@ -30,7 +30,7 @@ function resolveTargetPath(event) {
|
|
|
30
30
|
return stripped.replace(RE_IMAGE_EXT, "") || "/";
|
|
31
31
|
}
|
|
32
32
|
export default defineEventHandler(async (event) => {
|
|
33
|
-
const runtimeConfig = useOgImageRuntimeConfig();
|
|
33
|
+
const runtimeConfig = useOgImageRuntimeConfig(event);
|
|
34
34
|
const security = runtimeConfig.security;
|
|
35
35
|
if (!import.meta.prerender && !import.meta.dev && security?.restrictRuntimeImagesToOrigin) {
|
|
36
36
|
const requestHost = getRequestHost(event, { xForwardedHost: true });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getSiteConfig } from "#site-config/server/composables/getSiteConfig";
|
|
2
1
|
import { createError, getRequestHost, H3Error, setHeader } from "h3";
|
|
2
|
+
import { getSiteConfig } from "#site-config/server/composables/getSiteConfig";
|
|
3
3
|
import { logger } from "../../logger.js";
|
|
4
4
|
import { getBuildCachedImage, setBuildCachedImage } from "../og-image/cache/buildCache.js";
|
|
5
5
|
import { resolveContext } from "../og-image/context.js";
|
|
@@ -16,6 +16,7 @@ export async function imageEventHandler(e) {
|
|
|
16
16
|
if (ctx instanceof H3Error)
|
|
17
17
|
return ctx;
|
|
18
18
|
const timings = ctx.timings;
|
|
19
|
+
timings.record("resolve-context", performance.now() - reqStart);
|
|
19
20
|
try {
|
|
20
21
|
return await renderOgImage(e, ctx);
|
|
21
22
|
} finally {
|
|
@@ -28,7 +29,7 @@ export async function imageEventHandler(e) {
|
|
|
28
29
|
async function renderOgImage(e, ctx) {
|
|
29
30
|
const timings = ctx.timings;
|
|
30
31
|
const { isDevToolsContextRequest, extension, renderer } = ctx;
|
|
31
|
-
const { debug, baseCacheKey, security } = useOgImageRuntimeConfig();
|
|
32
|
+
const { debug, baseCacheKey, security } = useOgImageRuntimeConfig(e);
|
|
32
33
|
if (!import.meta.prerender && !import.meta.dev && security?.restrictRuntimeImagesToOrigin) {
|
|
33
34
|
const requestHost = getRequestHost(e, { xForwardedHost: true });
|
|
34
35
|
let requestHostname;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getNitroOrigin } from "#site-config/server/composables";
|
|
2
1
|
import { $fetch } from "ofetch";
|
|
2
|
+
import { getNitroOrigin } from "#site-config/server/composables";
|
|
3
3
|
import { tryCloudflareAssetsFetch } from "./cloudflareAssets.js";
|
|
4
4
|
export async function fetchLocalAsset(event, path, options) {
|
|
5
5
|
const { fetchTimeout, headers, includeExternalFallback = false, onStepFailure } = options;
|
|
@@ -2,5 +2,5 @@ import type { H3Event } from 'h3';
|
|
|
2
2
|
import type { NitroRouteRules } from 'nitropack';
|
|
3
3
|
import type { NuxtIslandResponse } from 'nuxt/app';
|
|
4
4
|
export { withoutQuery } from 'nuxtseo-shared/utils';
|
|
5
|
-
export declare function fetchIsland(e: H3Event, component: string, props: Record<string, any
|
|
5
|
+
export declare function fetchIsland(e: H3Event, component: string, props: Record<string, any>, timeout?: number): Promise<NuxtIslandResponse>;
|
|
6
6
|
export declare function createNitroRouteRuleMatcher(): ((path: string) => NitroRouteRules);
|
|
@@ -4,12 +4,15 @@ import { hash } from "ohash";
|
|
|
4
4
|
import { createRouter as createRadixRouter, toRouteMatcher } from "radix3";
|
|
5
5
|
import { withoutBase, withoutTrailingSlash } from "ufo";
|
|
6
6
|
export { withoutQuery } from "nuxtseo-shared/utils";
|
|
7
|
-
export function fetchIsland(e, component, props) {
|
|
7
|
+
export function fetchIsland(e, component, props, timeout) {
|
|
8
8
|
const hashId = hash([component, props]).replaceAll("_", "-");
|
|
9
|
+
const signal = timeout ? AbortSignal.timeout(timeout) : void 0;
|
|
9
10
|
return e.$fetch(`/__nuxt_island/${component}_${hashId}.json`, {
|
|
10
11
|
params: {
|
|
11
12
|
props: JSON.stringify(props)
|
|
12
|
-
}
|
|
13
|
+
},
|
|
14
|
+
timeout,
|
|
15
|
+
signal
|
|
13
16
|
});
|
|
14
17
|
}
|
|
15
18
|
export function createNitroRouteRuleMatcher() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { componentNames } from "#og-image-virtual/component-names.mjs";
|
|
2
1
|
import { createError } from "h3";
|
|
2
|
+
import { componentNames } from "#og-image-virtual/component-names.mjs";
|
|
3
3
|
const RENDERER_SUFFIXES = ["satori", "browser", "takumi"];
|
|
4
4
|
const RE_RENDERER_SUFFIX_DOT = /\.?(satori|browser|takumi)$/i;
|
|
5
5
|
const RE_RENDERER_SUFFIX_PASCAL = /(Satori|Browser|Takumi)$/;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expand an IPv6 string to its 8 16-bit groups. Accepts `::` shorthand,
|
|
3
|
+
* embedded IPv4 in the trailing 32 bits (`::ffff:1.2.3.4`), and zone IDs
|
|
4
|
+
* (stripped). Returns `null` for any malformed input.
|
|
5
|
+
*/
|
|
6
|
+
export declare function expandIPv6(addr: string): number[] | null;
|
|
7
|
+
/**
|
|
8
|
+
* Returns true if the URL points at an internal/private network or otherwise
|
|
9
|
+
* unsafe target. Only `http:` and `https:` are accepted; everything else is
|
|
10
|
+
* blocked outright.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isBlockedUrl(url: string): boolean;
|
|
13
|
+
export interface SafeFetchOptions {
|
|
14
|
+
timeout: number;
|
|
15
|
+
headers?: Record<string, string>;
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Fetch a URL with manual redirect handling. Each hop (including the initial
|
|
20
|
+
* URL) is run through `isBlockedUrl` before the request is dispatched, so an
|
|
21
|
+
* allowed origin returning a 30x to an internal IP cannot complete the SSRF.
|
|
22
|
+
*
|
|
23
|
+
* Returns `null` on any failure (block, network error, non-2xx, redirect
|
|
24
|
+
* limit). The caller treats null as a soft failure and falls back to the
|
|
25
|
+
* usual missing-asset behaviour.
|
|
26
|
+
*/
|
|
27
|
+
export declare function fetchWithRedirectValidation(initialUrl: string, opts: SafeFetchOptions): Promise<ArrayBuffer | null>;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
const RE_IPV6_BRACKETS = /^\[|\]$/g;
|
|
2
|
+
const RE_DIGIT_ONLY = /^\d+$/;
|
|
3
|
+
const RE_INT_IP = /^(?:0x[\da-f]+|\d+)$/i;
|
|
4
|
+
const RE_HEX_GROUP = /^[0-9a-f]{1,4}$/i;
|
|
5
|
+
const RE_IPV6_CHARS = /^[0-9a-f:.]+$/i;
|
|
6
|
+
const RE_TRAILING_V4 = /(\d+\.\d+\.\d+\.\d+)$/;
|
|
7
|
+
const MAX_REDIRECTS = 5;
|
|
8
|
+
function isPrivateIPv4(a, b) {
|
|
9
|
+
if (a === 127)
|
|
10
|
+
return true;
|
|
11
|
+
if (a === 10)
|
|
12
|
+
return true;
|
|
13
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
14
|
+
return true;
|
|
15
|
+
if (a === 192 && b === 168)
|
|
16
|
+
return true;
|
|
17
|
+
if (a === 169 && b === 254)
|
|
18
|
+
return true;
|
|
19
|
+
if (a === 0)
|
|
20
|
+
return true;
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
export function expandIPv6(addr) {
|
|
24
|
+
const noZone = addr.split("%")[0];
|
|
25
|
+
if (!noZone || !RE_IPV6_CHARS.test(noZone))
|
|
26
|
+
return null;
|
|
27
|
+
let work = noZone;
|
|
28
|
+
const v4Match = work.match(RE_TRAILING_V4);
|
|
29
|
+
if (v4Match) {
|
|
30
|
+
const octets = v4Match[1].split(".").map(Number);
|
|
31
|
+
if (octets.some((o) => !Number.isFinite(o) || o < 0 || o > 255))
|
|
32
|
+
return null;
|
|
33
|
+
const hi = (octets[0] << 8 | octets[1]).toString(16);
|
|
34
|
+
const lo = (octets[2] << 8 | octets[3]).toString(16);
|
|
35
|
+
work = `${work.slice(0, -v4Match[1].length) + hi}:${lo}`;
|
|
36
|
+
}
|
|
37
|
+
const parts = work.split("::");
|
|
38
|
+
if (parts.length > 2)
|
|
39
|
+
return null;
|
|
40
|
+
const head = parts[0] ? parts[0].split(":") : [];
|
|
41
|
+
const tail = parts[1] !== void 0 && parts[1] ? parts[1].split(":") : [];
|
|
42
|
+
if (parts.length === 1) {
|
|
43
|
+
if (head.length !== 8)
|
|
44
|
+
return null;
|
|
45
|
+
} else if (head.length + tail.length > 7) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const fillCount = parts.length === 2 ? 8 - head.length - tail.length : 0;
|
|
49
|
+
const allGroups = [...head, ...Array.from({ length: fillCount }).fill("0"), ...tail];
|
|
50
|
+
const result = [];
|
|
51
|
+
for (const g of allGroups) {
|
|
52
|
+
if (!RE_HEX_GROUP.test(g))
|
|
53
|
+
return null;
|
|
54
|
+
result.push(Number.parseInt(g, 16));
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
function isPrivateIPv6(groups) {
|
|
59
|
+
const [g0, g1, g2, g3, g4, g5, g6, g7] = groups;
|
|
60
|
+
if (g0 === 0 && g1 === 0 && g2 === 0 && g3 === 0 && g4 === 0 && g5 === 0 && g6 === 0 && g7 === 1)
|
|
61
|
+
return true;
|
|
62
|
+
if (g0 === 0 && g1 === 0 && g2 === 0 && g3 === 0 && g4 === 0 && g5 === 0 && g6 === 0 && g7 === 0)
|
|
63
|
+
return true;
|
|
64
|
+
if (g0 === 0 && g1 === 0 && g2 === 0 && g3 === 0 && g4 === 0 && g5 === 65535) {
|
|
65
|
+
const a = g6 >> 8 & 255;
|
|
66
|
+
const b = g6 & 255;
|
|
67
|
+
return isPrivateIPv4(a, b);
|
|
68
|
+
}
|
|
69
|
+
if (g0 === 100 && g1 === 65435 && g2 === 0 && g3 === 0 && g4 === 0 && g5 === 0) {
|
|
70
|
+
const a = g6 >> 8 & 255;
|
|
71
|
+
const b = g6 & 255;
|
|
72
|
+
return isPrivateIPv4(a, b);
|
|
73
|
+
}
|
|
74
|
+
if (g0 === 100 && g1 === 65435 && g2 === 1)
|
|
75
|
+
return true;
|
|
76
|
+
if ((g0 & 65024) === 64512)
|
|
77
|
+
return true;
|
|
78
|
+
if ((g0 & 65472) === 65152)
|
|
79
|
+
return true;
|
|
80
|
+
if ((g0 & 65472) === 65216)
|
|
81
|
+
return true;
|
|
82
|
+
if (g0 === 8193 && g1 === 3512)
|
|
83
|
+
return true;
|
|
84
|
+
if ((g0 & 65520) === 16368)
|
|
85
|
+
return true;
|
|
86
|
+
if (g0 === 24320)
|
|
87
|
+
return true;
|
|
88
|
+
if ((g0 & 65280) === 65280)
|
|
89
|
+
return true;
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
export function isBlockedUrl(url) {
|
|
93
|
+
let parsed;
|
|
94
|
+
try {
|
|
95
|
+
parsed = new URL(url);
|
|
96
|
+
} catch {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
|
|
100
|
+
return true;
|
|
101
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
102
|
+
const bare = hostname.replace(RE_IPV6_BRACKETS, "");
|
|
103
|
+
if (bare === "localhost" || bare.endsWith(".localhost"))
|
|
104
|
+
return true;
|
|
105
|
+
const dottedParts = bare.split(".");
|
|
106
|
+
if (dottedParts.length === 4 && dottedParts.every((p) => RE_DIGIT_ONLY.test(p))) {
|
|
107
|
+
const octets = dottedParts.map(Number);
|
|
108
|
+
if (octets.some((o) => o > 255))
|
|
109
|
+
return true;
|
|
110
|
+
return isPrivateIPv4(octets[0], octets[1]);
|
|
111
|
+
}
|
|
112
|
+
if (RE_INT_IP.test(bare)) {
|
|
113
|
+
const num = Number(bare);
|
|
114
|
+
if (Number.isFinite(num) && num >= 0 && num <= 4294967295)
|
|
115
|
+
return isPrivateIPv4(num >>> 24 & 255, num >>> 16 & 255);
|
|
116
|
+
}
|
|
117
|
+
if (bare.includes(":")) {
|
|
118
|
+
const groups = expandIPv6(bare);
|
|
119
|
+
if (groups)
|
|
120
|
+
return isPrivateIPv6(groups);
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
export async function fetchWithRedirectValidation(initialUrl, opts) {
|
|
126
|
+
const controller = new AbortController();
|
|
127
|
+
const externalAbort = opts.signal;
|
|
128
|
+
const onExternalAbort = () => controller.abort(externalAbort?.reason);
|
|
129
|
+
if (externalAbort) {
|
|
130
|
+
if (externalAbort.aborted)
|
|
131
|
+
controller.abort(externalAbort.reason);
|
|
132
|
+
else
|
|
133
|
+
externalAbort.addEventListener("abort", onExternalAbort, { once: true });
|
|
134
|
+
}
|
|
135
|
+
const timer = setTimeout(() => controller.abort(new Error("timeout")), opts.timeout);
|
|
136
|
+
try {
|
|
137
|
+
let url = initialUrl;
|
|
138
|
+
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
139
|
+
if (isBlockedUrl(url))
|
|
140
|
+
return null;
|
|
141
|
+
const res = await fetch(url, {
|
|
142
|
+
redirect: "manual",
|
|
143
|
+
signal: controller.signal,
|
|
144
|
+
headers: opts.headers
|
|
145
|
+
});
|
|
146
|
+
if (res.status >= 300 && res.status < 400) {
|
|
147
|
+
const loc = res.headers.get("location");
|
|
148
|
+
if (!loc)
|
|
149
|
+
return null;
|
|
150
|
+
url = new URL(loc, url).toString();
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (!res.ok)
|
|
154
|
+
return null;
|
|
155
|
+
return await res.arrayBuffer();
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
} catch {
|
|
159
|
+
return null;
|
|
160
|
+
} finally {
|
|
161
|
+
clearTimeout(timer);
|
|
162
|
+
if (externalAbort)
|
|
163
|
+
externalAbort.removeEventListener("abort", onExternalAbort);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function withTimeout<T>(promise: Promise<T> | T, ms: number, label: string): Promise<T>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function withTimeout(promise, ms, label) {
|
|
2
|
+
let timer;
|
|
3
|
+
return Promise.race([
|
|
4
|
+
Promise.resolve(promise),
|
|
5
|
+
new Promise((_, reject) => {
|
|
6
|
+
timer = setTimeout(
|
|
7
|
+
() => reject(new Error(`${label} timed out after ${ms}ms`)),
|
|
8
|
+
ms
|
|
9
|
+
);
|
|
10
|
+
})
|
|
11
|
+
]).finally(() => clearTimeout(timer));
|
|
12
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { componentNames } from "#og-image-virtual/component-names.mjs";
|
|
2
1
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
2
|
import { joinURL } from "ufo";
|
|
3
|
+
import { componentNames } from "#og-image-virtual/component-names.mjs";
|
|
4
4
|
import { buildOgImageUrl } from "../shared.js";
|
|
5
5
|
export function getOgImagePath(_pagePath, _options) {
|
|
6
6
|
const baseURL = useRuntimeConfig().app.baseURL;
|
|
@@ -20,9 +20,13 @@ export function getOgImagePath(_pagePath, _options) {
|
|
|
20
20
|
}
|
|
21
21
|
export function useOgImageRuntimeConfig(e) {
|
|
22
22
|
const c = useRuntimeConfig(e);
|
|
23
|
+
const moduleCfg = c["nuxt-og-image"] || {};
|
|
24
|
+
const overrideSecret = c.ogImage?.secret;
|
|
25
|
+
const security = overrideSecret ? { ...moduleCfg.security || {}, secret: overrideSecret } : moduleCfg.security;
|
|
23
26
|
return {
|
|
24
27
|
defaults: {},
|
|
25
|
-
...
|
|
28
|
+
...moduleCfg,
|
|
29
|
+
security,
|
|
26
30
|
app: {
|
|
27
31
|
baseURL: c.app.baseURL
|
|
28
32
|
}
|
|
@@ -5067,7 +5067,7 @@ export const resolve = (import.meta.dev || import.meta.prerender) ? devResolve :
|
|
|
5067
5067
|
if (hasNuxtFonts && fontContext) {
|
|
5068
5068
|
persistFontUrlMapping({ fontContext, buildDir: nuxt.options.buildDir, logger });
|
|
5069
5069
|
}
|
|
5070
|
-
if (!fontProcessingDone && convertedWoff2Files.size === 0 && hasSatoriRenderer() && hasNuxtFonts) {
|
|
5070
|
+
if (!fontProcessingDone && convertedWoff2Files.size === 0 && (hasSatoriRenderer() || hasTakumiRenderer()) && hasNuxtFonts) {
|
|
5071
5071
|
if (pendingFontRequirements.length > 0)
|
|
5072
5072
|
await Promise.all(pendingFontRequirements);
|
|
5073
5073
|
await convertWoff2ToTtf({
|
|
@@ -5136,7 +5136,7 @@ export const rootDir = ${JSON.stringify(nuxt.options.rootDir)}`;
|
|
|
5136
5136
|
});
|
|
5137
5137
|
nuxt.hook("vite:compiled", async () => {
|
|
5138
5138
|
persistFontUrlMapping({ fontContext, buildDir: nuxt.options.buildDir, logger });
|
|
5139
|
-
if (fontProcessingDone || !hasSatoriRenderer())
|
|
5139
|
+
if (fontProcessingDone || !hasSatoriRenderer() && !hasTakumiRenderer())
|
|
5140
5140
|
return;
|
|
5141
5141
|
if (pendingFontRequirements.length === 0)
|
|
5142
5142
|
return;
|
|
@@ -5240,6 +5240,11 @@ export const rootDir = ${JSON.stringify(nuxt.options.rootDir)}`;
|
|
|
5240
5240
|
}
|
|
5241
5241
|
nuxt.hooks.callHook("nuxt-og-image:runtime-config", runtimeConfig);
|
|
5242
5242
|
nuxt.options.runtimeConfig["nuxt-og-image"] = runtimeConfig;
|
|
5243
|
+
const existingOgImageCfg = nuxt.options.runtimeConfig.ogImage;
|
|
5244
|
+
nuxt.options.runtimeConfig.ogImage = {
|
|
5245
|
+
...existingOgImageCfg && typeof existingOgImageCfg === "object" ? existingOgImageCfg : {},
|
|
5246
|
+
secret: existingOgImageCfg?.secret || config.security?.secret || process.env.NUXT_OG_IMAGE_SECRET || ""
|
|
5247
|
+
};
|
|
5243
5248
|
nuxt.options.runtimeConfig.public = {
|
|
5244
5249
|
...nuxt.options.runtimeConfig.public,
|
|
5245
5250
|
"nuxt-og-image": {
|
|
@@ -4320,7 +4320,7 @@ const module$1 = kit.defineNuxtModule({
|
|
|
4320
4320
|
await onUpgrade(nuxt, options, previousVersion);
|
|
4321
4321
|
},
|
|
4322
4322
|
async setup(config, nuxt) {
|
|
4323
|
-
const _resolver = kit.createResolver((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('shared/nuxt-og-image.
|
|
4323
|
+
const _resolver = kit.createResolver((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('shared/nuxt-og-image.DnwTVrnM.cjs', document.baseURI).href)));
|
|
4324
4324
|
const fixSharedPath = (p) => {
|
|
4325
4325
|
if (p.includes("/shared/runtime/"))
|
|
4326
4326
|
return p.replace("/shared/runtime/", "/runtime/");
|
|
@@ -5087,7 +5087,7 @@ export const resolve = (import.meta.dev || import.meta.prerender) ? devResolve :
|
|
|
5087
5087
|
if (hasNuxtFonts && fontContext) {
|
|
5088
5088
|
persistFontUrlMapping({ fontContext, buildDir: nuxt.options.buildDir, logger: logger_js.logger });
|
|
5089
5089
|
}
|
|
5090
|
-
if (!fontProcessingDone && convertedWoff2Files.size === 0 && hasSatoriRenderer() && hasNuxtFonts) {
|
|
5090
|
+
if (!fontProcessingDone && convertedWoff2Files.size === 0 && (hasSatoriRenderer() || hasTakumiRenderer()) && hasNuxtFonts) {
|
|
5091
5091
|
if (pendingFontRequirements.length > 0)
|
|
5092
5092
|
await Promise.all(pendingFontRequirements);
|
|
5093
5093
|
await convertWoff2ToTtf({
|
|
@@ -5156,7 +5156,7 @@ export const rootDir = ${JSON.stringify(nuxt.options.rootDir)}`;
|
|
|
5156
5156
|
});
|
|
5157
5157
|
nuxt.hook("vite:compiled", async () => {
|
|
5158
5158
|
persistFontUrlMapping({ fontContext, buildDir: nuxt.options.buildDir, logger: logger_js.logger });
|
|
5159
|
-
if (fontProcessingDone || !hasSatoriRenderer())
|
|
5159
|
+
if (fontProcessingDone || !hasSatoriRenderer() && !hasTakumiRenderer())
|
|
5160
5160
|
return;
|
|
5161
5161
|
if (pendingFontRequirements.length === 0)
|
|
5162
5162
|
return;
|
|
@@ -5260,6 +5260,11 @@ export const rootDir = ${JSON.stringify(nuxt.options.rootDir)}`;
|
|
|
5260
5260
|
}
|
|
5261
5261
|
nuxt.hooks.callHook("nuxt-og-image:runtime-config", runtimeConfig);
|
|
5262
5262
|
nuxt.options.runtimeConfig["nuxt-og-image"] = runtimeConfig;
|
|
5263
|
+
const existingOgImageCfg = nuxt.options.runtimeConfig.ogImage;
|
|
5264
|
+
nuxt.options.runtimeConfig.ogImage = {
|
|
5265
|
+
...existingOgImageCfg && typeof existingOgImageCfg === "object" ? existingOgImageCfg : {},
|
|
5266
|
+
secret: existingOgImageCfg?.secret || config.security?.secret || process.env.NUXT_OG_IMAGE_SECRET || ""
|
|
5267
|
+
};
|
|
5263
5268
|
nuxt.options.runtimeConfig.public = {
|
|
5264
5269
|
...nuxt.options.runtimeConfig.public,
|
|
5265
5270
|
"nuxt-og-image": {
|