@nuxt/scripts 1.0.0-beta.20 → 1.0.0-beta.22
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/client/200.html +1 -1
- package/dist/client/404.html +1 -1
- package/dist/client/_nuxt/{DMlY-BNa.js → B7aPLMNo.js} +1 -1
- package/dist/client/_nuxt/BNNMZFwZ.js +162 -0
- package/dist/client/_nuxt/{__ZZTkMj.js → Bh9fd9qr.js} +1 -1
- package/dist/client/_nuxt/{9LJPrOyI.js → UTi7FhVv.js} +1 -1
- package/dist/client/_nuxt/builds/latest.json +1 -1
- package/dist/client/_nuxt/builds/meta/7b372941-1db0-4ea4-80d2-a41f53088a98.json +1 -0
- package/dist/client/_nuxt/error-404.DMdWw4vT.css +1 -0
- package/dist/client/_nuxt/error-500.CROTF27X.css +1 -0
- package/dist/client/index.html +1 -1
- package/dist/module.d.mts +2 -2
- package/dist/module.d.ts +2 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +75 -51
- package/dist/registry.mjs +65 -4
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.d.vue.ts +3 -3
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.vue +14 -1
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.vue.d.ts +3 -3
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.d.vue.ts +2 -2
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue.d.ts +2 -2
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarker.d.vue.ts +2 -2
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarker.vue.d.ts +2 -2
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPolygon.d.vue.ts +2 -2
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPolygon.vue.d.ts +2 -2
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPolyline.d.vue.ts +2 -2
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPolyline.vue.d.ts +2 -2
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsRectangle.d.vue.ts +2 -2
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsRectangle.vue.d.ts +2 -2
- package/dist/runtime/components/ScriptBlueskyEmbed.d.vue.ts +87 -0
- package/dist/runtime/components/ScriptBlueskyEmbed.vue +85 -0
- package/dist/runtime/components/ScriptBlueskyEmbed.vue.d.ts +87 -0
- package/dist/runtime/components/ScriptInstagramEmbed.d.vue.ts +2 -2
- package/dist/runtime/components/ScriptInstagramEmbed.vue +4 -1
- package/dist/runtime/components/ScriptInstagramEmbed.vue.d.ts +2 -2
- package/dist/runtime/components/ScriptXEmbed.d.vue.ts +2 -2
- package/dist/runtime/components/ScriptXEmbed.vue +5 -2
- package/dist/runtime/components/ScriptXEmbed.vue.d.ts +2 -2
- package/dist/runtime/registry/bluesky-embed.d.ts +116 -0
- package/dist/runtime/registry/bluesky-embed.js +72 -0
- package/dist/runtime/registry/gravatar.js +2 -2
- package/dist/runtime/registry/schemas.d.ts +20 -3
- package/dist/runtime/registry/schemas.js +20 -3
- package/dist/runtime/registry/x-embed.js +1 -1
- package/dist/runtime/server/bluesky-embed-image.d.ts +2 -0
- package/dist/runtime/server/bluesky-embed-image.js +7 -0
- package/dist/runtime/server/bluesky-embed.d.ts +16 -0
- package/dist/runtime/server/bluesky-embed.js +59 -0
- package/dist/runtime/server/google-maps-geocode-proxy.d.ts +2 -0
- package/dist/runtime/server/google-maps-geocode-proxy.js +34 -0
- package/dist/runtime/server/google-static-maps-proxy.js +1 -12
- package/dist/runtime/server/gravatar-proxy.js +1 -16
- package/dist/runtime/server/instagram-embed-asset.js +8 -42
- package/dist/runtime/server/instagram-embed-image.js +6 -54
- package/dist/runtime/server/instagram-embed.d.ts +16 -0
- package/dist/runtime/server/instagram-embed.js +171 -42
- package/dist/runtime/server/utils/image-proxy.d.ts +12 -0
- package/dist/runtime/server/utils/image-proxy.js +70 -0
- package/dist/runtime/server/x-embed-image.js +5 -49
- package/dist/runtime/types.d.ts +16 -0
- package/dist/runtime/utils.d.ts +1 -0
- package/dist/runtime/utils.js +11 -2
- package/dist/stats.mjs +1 -1
- package/dist/types-source.mjs +18 -6
- package/package.json +10 -9
- package/dist/client/_nuxt/DFEfk2pB.js +0 -162
- package/dist/client/_nuxt/builds/meta/8212d4fa-7985-421b-815a-03a886e667d4.json +0 -1
- package/dist/client/_nuxt/error-404.CHeaW3dp.css +0 -1
- package/dist/client/_nuxt/error-500.DvOvWme_.css +0 -1
|
@@ -1,16 +1,128 @@
|
|
|
1
1
|
import { createError, defineEventHandler, getQuery, setHeader } from "h3";
|
|
2
2
|
import { $fetch } from "ofetch";
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
3
|
+
import { ELEMENT_NODE, parse, renderSync, TEXT_NODE, walkSync } from "ultrahtml";
|
|
4
|
+
export const RSRC_RE = /url\(\/rsrc\.php([^)]+)\)/g;
|
|
5
|
+
export const AMP_RE = /&/g;
|
|
6
|
+
export const SCONTENT_RE = /https:\/\/scontent[^"'\s),]+\.cdninstagram\.com[^"'\s),]+/g;
|
|
7
|
+
export const STATIC_CDN_RE = /https:\/\/static\.cdninstagram\.com[^"'\s),]+/g;
|
|
8
|
+
export const LOOKASIDE_RE = /https:\/\/lookaside\.instagram\.com[^"'\s),]+/g;
|
|
9
|
+
export const INSTAGRAM_IMAGE_HOSTS = ["scontent.cdninstagram.com", "lookaside.instagram.com"];
|
|
10
|
+
export const INSTAGRAM_ASSET_HOST = "static.cdninstagram.com";
|
|
11
|
+
const CHARSET_RE = /@charset\s[^;]+;/gi;
|
|
12
|
+
const IMPORT_RE = /@import\s[^;]+;/gi;
|
|
13
|
+
const WHITESPACE_RE = /\s/;
|
|
14
|
+
const AT_RULE_NAME_RE = /@([\w-]+)/;
|
|
15
|
+
const MULTI_SPACE_RE = /\s+/g;
|
|
16
|
+
const SRCSET_SPLIT_RE = /\s+/;
|
|
17
|
+
export function proxyImageUrl(url) {
|
|
18
|
+
return `/_scripts/embed/instagram-image?url=${encodeURIComponent(url.replace(AMP_RE, "&"))}`;
|
|
19
|
+
}
|
|
20
|
+
export function proxyAssetUrl(url) {
|
|
21
|
+
return `/_scripts/embed/instagram-asset?url=${encodeURIComponent(url.replace(AMP_RE, "&"))}`;
|
|
22
|
+
}
|
|
23
|
+
export function rewriteUrl(url) {
|
|
24
|
+
try {
|
|
25
|
+
const parsed = new URL(url);
|
|
26
|
+
if (parsed.hostname === INSTAGRAM_ASSET_HOST)
|
|
27
|
+
return proxyAssetUrl(url);
|
|
28
|
+
if (INSTAGRAM_IMAGE_HOSTS.some((h) => parsed.hostname === h || parsed.hostname.endsWith(`.cdninstagram.com`)))
|
|
29
|
+
return proxyImageUrl(url);
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
return url;
|
|
33
|
+
}
|
|
34
|
+
export function rewriteUrlsInText(text) {
|
|
35
|
+
return text.replace(SCONTENT_RE, (m) => proxyImageUrl(m)).replace(STATIC_CDN_RE, (m) => proxyAssetUrl(m)).replace(LOOKASIDE_RE, (m) => proxyImageUrl(m));
|
|
36
|
+
}
|
|
37
|
+
function removeNode(node) {
|
|
38
|
+
node.type = TEXT_NODE;
|
|
39
|
+
node.value = "";
|
|
40
|
+
node.name = void 0;
|
|
41
|
+
node.attributes = {};
|
|
42
|
+
node.children = [];
|
|
43
|
+
}
|
|
44
|
+
export function scopeCss(css, scopeSelector) {
|
|
45
|
+
let result = css.replace(CHARSET_RE, "");
|
|
46
|
+
result = result.replace(IMPORT_RE, "");
|
|
47
|
+
return processRules(result, scopeSelector);
|
|
48
|
+
}
|
|
49
|
+
function processRules(css, scopeSelector) {
|
|
50
|
+
const output = [];
|
|
51
|
+
let i = 0;
|
|
52
|
+
while (i < css.length) {
|
|
53
|
+
while (i < css.length && WHITESPACE_RE.test(css[i])) i++;
|
|
54
|
+
if (i >= css.length)
|
|
55
|
+
break;
|
|
56
|
+
if (css[i] === "@") {
|
|
57
|
+
const atRule = extractAtRule(css, i);
|
|
58
|
+
if (atRule) {
|
|
59
|
+
const atName = atRule.content.match(AT_RULE_NAME_RE)?.[1]?.toLowerCase();
|
|
60
|
+
if (atName === "media" || atName === "supports" || atName === "layer") {
|
|
61
|
+
const braceStart = atRule.content.indexOf("{");
|
|
62
|
+
const innerCss = atRule.content.slice(braceStart + 1, -1);
|
|
63
|
+
const scopedInner = processRules(innerCss, scopeSelector);
|
|
64
|
+
output.push(`${atRule.content.slice(0, braceStart + 1) + scopedInner}}`);
|
|
65
|
+
} else if (atName === "keyframes" || atName === "-webkit-keyframes" || atName === "font-face") {
|
|
66
|
+
output.push(atRule.content);
|
|
67
|
+
}
|
|
68
|
+
i = atRule.end;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const bracePos = css.indexOf("{", i);
|
|
73
|
+
if (bracePos === -1)
|
|
74
|
+
break;
|
|
75
|
+
const selector = css.slice(i, bracePos).trim();
|
|
76
|
+
const block = extractBlock(css, bracePos);
|
|
77
|
+
if (!block)
|
|
78
|
+
break;
|
|
79
|
+
i = block.end;
|
|
80
|
+
if (!selector)
|
|
81
|
+
continue;
|
|
82
|
+
const selectors = selector.split(",").map((s) => s.trim());
|
|
83
|
+
const filteredSelectors = selectors.filter((s) => {
|
|
84
|
+
const normalized = s.replace(MULTI_SPACE_RE, " ").trim().toLowerCase();
|
|
85
|
+
return normalized !== ":root" && normalized !== "html" && normalized !== "body" && !normalized.startsWith(":root ") && !normalized.startsWith("html ") && !normalized.startsWith("body ") && normalized !== "html, body";
|
|
86
|
+
});
|
|
87
|
+
if (filteredSelectors.length === 0)
|
|
88
|
+
continue;
|
|
89
|
+
const scopedSelectors = filteredSelectors.map((s) => {
|
|
90
|
+
return `${scopeSelector} ${s}`;
|
|
91
|
+
});
|
|
92
|
+
output.push(`${scopedSelectors.join(", ")} ${block.content}`);
|
|
93
|
+
}
|
|
94
|
+
return output.join("\n");
|
|
95
|
+
}
|
|
96
|
+
function extractAtRule(css, start) {
|
|
97
|
+
const bracePos = css.indexOf("{", start);
|
|
98
|
+
const semiPos = css.indexOf(";", start);
|
|
99
|
+
if (semiPos !== -1 && (bracePos === -1 || semiPos < bracePos)) {
|
|
100
|
+
return { content: css.slice(start, semiPos + 1), end: semiPos + 1 };
|
|
101
|
+
}
|
|
102
|
+
if (bracePos === -1)
|
|
103
|
+
return null;
|
|
104
|
+
const block = extractBlock(css, bracePos);
|
|
105
|
+
if (!block)
|
|
106
|
+
return null;
|
|
107
|
+
return {
|
|
108
|
+
content: css.slice(start, bracePos) + block.content,
|
|
109
|
+
end: block.end
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function extractBlock(css, openBrace) {
|
|
113
|
+
let depth = 0;
|
|
114
|
+
for (let j = openBrace; j < css.length; j++) {
|
|
115
|
+
if (css[j] === "{") {
|
|
116
|
+
depth++;
|
|
117
|
+
} else if (css[j] === "}") {
|
|
118
|
+
depth--;
|
|
119
|
+
if (depth === 0) {
|
|
120
|
+
return { content: css.slice(openBrace, j + 1), end: j + 1 };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
14
126
|
export default defineEventHandler(async (event) => {
|
|
15
127
|
const query = getQuery(event);
|
|
16
128
|
const postUrl = query.url;
|
|
@@ -42,7 +154,6 @@ export default defineEventHandler(async (event) => {
|
|
|
42
154
|
const html = await $fetch(embedUrl, {
|
|
43
155
|
headers: {
|
|
44
156
|
"Accept": "text/html",
|
|
45
|
-
// Use simple UA - full Chrome UA triggers JS-heavy version without static content
|
|
46
157
|
"User-Agent": "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
|
|
47
158
|
}
|
|
48
159
|
}).catch((error) => {
|
|
@@ -51,16 +162,45 @@ export default defineEventHandler(async (event) => {
|
|
|
51
162
|
statusMessage: error.statusMessage || "Failed to fetch Instagram embed"
|
|
52
163
|
});
|
|
53
164
|
});
|
|
165
|
+
const ast = parse(html);
|
|
54
166
|
const cssUrls = [];
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
167
|
+
walkSync(ast, (node) => {
|
|
168
|
+
if (node.type !== ELEMENT_NODE)
|
|
169
|
+
return;
|
|
170
|
+
if (node.name === "link" && node.attributes.rel === "stylesheet" && node.attributes.href) {
|
|
171
|
+
cssUrls.push(node.attributes.href);
|
|
172
|
+
removeNode(node);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (node.name === "script" || node.name === "noscript" || node.name === "style") {
|
|
176
|
+
removeNode(node);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
for (const attr of ["src", "poster"]) {
|
|
180
|
+
if (node.attributes[attr])
|
|
181
|
+
node.attributes[attr] = rewriteUrl(node.attributes[attr]);
|
|
182
|
+
}
|
|
183
|
+
if (node.attributes.srcset) {
|
|
184
|
+
node.attributes.srcset = node.attributes.srcset.split(",").map((entry) => {
|
|
185
|
+
const parts = entry.trim().split(SRCSET_SPLIT_RE);
|
|
186
|
+
const url = parts[0];
|
|
187
|
+
const descriptor = parts.slice(1).join(" ");
|
|
188
|
+
return url ? `${rewriteUrl(url)}${descriptor ? ` ${descriptor}` : ""}` : entry;
|
|
189
|
+
}).join(", ");
|
|
190
|
+
}
|
|
191
|
+
if (node.attributes.style)
|
|
192
|
+
node.attributes.style = rewriteUrlsInText(node.attributes.style);
|
|
193
|
+
});
|
|
194
|
+
walkSync(ast, (node) => {
|
|
195
|
+
if (node.type === TEXT_NODE && node.value)
|
|
196
|
+
node.value = rewriteUrlsInText(node.value);
|
|
197
|
+
});
|
|
198
|
+
let bodyNode = null;
|
|
199
|
+
walkSync(ast, (node) => {
|
|
200
|
+
if (node.type === ELEMENT_NODE && node.name === "body")
|
|
201
|
+
bodyNode = node;
|
|
202
|
+
});
|
|
203
|
+
const bodyHtml = bodyNode ? bodyNode.children.map((child) => renderSync(child)).join("") : renderSync(ast);
|
|
64
204
|
const cssContents = await Promise.all(
|
|
65
205
|
cssUrls.map(
|
|
66
206
|
(url) => $fetch(url, {
|
|
@@ -71,30 +211,19 @@ export default defineEventHandler(async (event) => {
|
|
|
71
211
|
let combinedCss = cssContents.join("\n");
|
|
72
212
|
combinedCss = combinedCss.replace(
|
|
73
213
|
RSRC_RE,
|
|
74
|
-
(_m, path) => `url(/
|
|
214
|
+
(_m, path) => `url(/_scripts/embed/instagram-asset?url=${encodeURIComponent(`https://static.cdninstagram.com/rsrc.php${path}`)})`
|
|
75
215
|
);
|
|
216
|
+
combinedCss = rewriteUrlsInText(combinedCss);
|
|
217
|
+
combinedCss = scopeCss(combinedCss, ".instagram-embed-root");
|
|
76
218
|
const baseStyles = `
|
|
77
|
-
|
|
78
|
-
#splash-screen { display: none !important; }
|
|
79
|
-
.Embed { opacity: 1 !important; visibility: visible !important; }
|
|
80
|
-
.EmbeddedMedia, .EmbeddedMediaImage { display: block !important; visibility: visible !important; }
|
|
219
|
+
.instagram-embed-root { background: white; max-width: 540px; width: calc(100% - 2px); border-radius: 3px; border: 1px solid rgb(219, 219, 219); display: block; margin: 0px 0px 12px; min-width: 326px; padding: 0px; }
|
|
220
|
+
.instagram-embed-root #splash-screen { display: none !important; }
|
|
221
|
+
.instagram-embed-root .Embed { opacity: 1 !important; visibility: visible !important; }
|
|
222
|
+
.instagram-embed-root .EmbeddedMedia, .instagram-embed-root .EmbeddedMediaImage { display: block !important; visibility: visible !important; }
|
|
81
223
|
`;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
(m) => `/api/_scripts/instagram-embed-image?url=${encodeURIComponent(m.replace(AMP_RE, "&"))}`
|
|
85
|
-
).replace(
|
|
86
|
-
STATIC_CDN_RE,
|
|
87
|
-
(m) => `/api/_scripts/instagram-embed-asset?url=${encodeURIComponent(m.replace(AMP_RE, "&"))}`
|
|
88
|
-
).replace(
|
|
89
|
-
LOOKASIDE_RE,
|
|
90
|
-
(m) => `/api/_scripts/instagram-embed-image?url=${encodeURIComponent(m.replace(AMP_RE, "&"))}`
|
|
91
|
-
);
|
|
92
|
-
rewrittenHtml = rewrittenHtml.replace(
|
|
93
|
-
"</head>",
|
|
94
|
-
`<style>${baseStyles}
|
|
95
|
-
${combinedCss}</style></head>`
|
|
96
|
-
);
|
|
224
|
+
const result = `<div class="instagram-embed-root"><style>${baseStyles}
|
|
225
|
+
${combinedCss}</style>${bodyHtml}</div>`;
|
|
97
226
|
setHeader(event, "Content-Type", "text/html");
|
|
98
227
|
setHeader(event, "Cache-Control", "public, max-age=600, s-maxage=600");
|
|
99
|
-
return
|
|
228
|
+
return result;
|
|
100
229
|
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ImageProxyConfig {
|
|
2
|
+
allowedDomains: string[] | ((hostname: string) => boolean);
|
|
3
|
+
accept?: string;
|
|
4
|
+
userAgent?: string;
|
|
5
|
+
cacheMaxAge?: number;
|
|
6
|
+
contentType?: string;
|
|
7
|
+
/** Follow redirects (default: true). Set to false to reject redirects (SSRF protection). */
|
|
8
|
+
followRedirects?: boolean;
|
|
9
|
+
/** Decode & in URL query parameter */
|
|
10
|
+
decodeAmpersands?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function createImageProxyHandler(config: ImageProxyConfig): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createError, defineEventHandler, getQuery, setHeader } from "h3";
|
|
2
|
+
import { $fetch } from "ofetch";
|
|
3
|
+
const AMP_RE = /&/g;
|
|
4
|
+
export function createImageProxyHandler(config) {
|
|
5
|
+
const {
|
|
6
|
+
accept = "image/webp,image/jpeg,image/png,image/*,*/*;q=0.8",
|
|
7
|
+
userAgent,
|
|
8
|
+
cacheMaxAge = 3600,
|
|
9
|
+
contentType = "image/jpeg",
|
|
10
|
+
followRedirects = true,
|
|
11
|
+
decodeAmpersands = false
|
|
12
|
+
} = config;
|
|
13
|
+
return defineEventHandler(async (event) => {
|
|
14
|
+
const query = getQuery(event);
|
|
15
|
+
let url = query.url;
|
|
16
|
+
if (decodeAmpersands && url)
|
|
17
|
+
url = url.replace(AMP_RE, "&");
|
|
18
|
+
if (!url) {
|
|
19
|
+
throw createError({
|
|
20
|
+
statusCode: 400,
|
|
21
|
+
statusMessage: "Image URL is required"
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
let parsedUrl;
|
|
25
|
+
try {
|
|
26
|
+
parsedUrl = new URL(url);
|
|
27
|
+
} catch {
|
|
28
|
+
throw createError({
|
|
29
|
+
statusCode: 400,
|
|
30
|
+
statusMessage: "Invalid image URL"
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
|
|
34
|
+
throw createError({
|
|
35
|
+
statusCode: 400,
|
|
36
|
+
statusMessage: "Invalid URL scheme"
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const domainAllowed = typeof config.allowedDomains === "function" ? config.allowedDomains(parsedUrl.hostname) : config.allowedDomains.includes(parsedUrl.hostname);
|
|
40
|
+
if (!domainAllowed) {
|
|
41
|
+
throw createError({
|
|
42
|
+
statusCode: 403,
|
|
43
|
+
statusMessage: "Domain not allowed"
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const headers = { Accept: accept };
|
|
47
|
+
if (userAgent)
|
|
48
|
+
headers["User-Agent"] = userAgent;
|
|
49
|
+
const response = await $fetch.raw(url, {
|
|
50
|
+
timeout: 5e3,
|
|
51
|
+
redirect: followRedirects ? "follow" : "manual",
|
|
52
|
+
ignoreResponseError: !followRedirects,
|
|
53
|
+
headers
|
|
54
|
+
}).catch((error) => {
|
|
55
|
+
throw createError({
|
|
56
|
+
statusCode: error.statusCode || 500,
|
|
57
|
+
statusMessage: error.statusMessage || "Failed to fetch image"
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
if (!followRedirects && response.status >= 300 && response.status < 400) {
|
|
61
|
+
throw createError({
|
|
62
|
+
statusCode: 403,
|
|
63
|
+
statusMessage: "Redirects not allowed"
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
setHeader(event, "Content-Type", response.headers.get("content-type") || contentType);
|
|
67
|
+
setHeader(event, "Cache-Control", `public, max-age=${cacheMaxAge}, s-maxage=${cacheMaxAge}`);
|
|
68
|
+
return response._data;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -1,53 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const query = getQuery(event);
|
|
5
|
-
const url = query.url;
|
|
6
|
-
if (!url) {
|
|
7
|
-
throw createError({
|
|
8
|
-
statusCode: 400,
|
|
9
|
-
statusMessage: "Image URL is required"
|
|
10
|
-
});
|
|
11
|
-
}
|
|
12
|
-
let parsedUrl;
|
|
13
|
-
try {
|
|
14
|
-
parsedUrl = new URL(url);
|
|
15
|
-
} catch {
|
|
16
|
-
throw createError({
|
|
17
|
-
statusCode: 400,
|
|
18
|
-
statusMessage: "Invalid image URL"
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
|
|
22
|
-
throw createError({
|
|
23
|
-
statusCode: 400,
|
|
24
|
-
statusMessage: "Invalid URL scheme"
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
const allowedDomains = [
|
|
1
|
+
import { createImageProxyHandler } from "./utils/image-proxy.js";
|
|
2
|
+
export default createImageProxyHandler({
|
|
3
|
+
allowedDomains: [
|
|
28
4
|
"pbs.twimg.com",
|
|
29
5
|
"abs.twimg.com",
|
|
30
6
|
"video.twimg.com"
|
|
31
|
-
]
|
|
32
|
-
|
|
33
|
-
throw createError({
|
|
34
|
-
statusCode: 403,
|
|
35
|
-
statusMessage: "Domain not allowed"
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
const response = await $fetch.raw(url, {
|
|
39
|
-
timeout: 5e3,
|
|
40
|
-
headers: {
|
|
41
|
-
"Accept": "image/webp,image/jpeg,image/png,image/*,*/*;q=0.8",
|
|
42
|
-
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
|
|
43
|
-
}
|
|
44
|
-
}).catch((error) => {
|
|
45
|
-
throw createError({
|
|
46
|
-
statusCode: error.statusCode || 500,
|
|
47
|
-
statusMessage: error.statusMessage || "Failed to fetch image"
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
setHeader(event, "Content-Type", response.headers.get("content-type") || "image/jpeg");
|
|
51
|
-
setHeader(event, "Cache-Control", "public, max-age=3600, s-maxage=3600");
|
|
52
|
-
return response._data;
|
|
7
|
+
],
|
|
8
|
+
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
|
|
53
9
|
});
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { Script } from '@unhead/vue/types';
|
|
|
3
3
|
import type { Import } from 'unimport';
|
|
4
4
|
import type { InferInput, ObjectSchema, UnionSchema, ValiError } from 'valibot';
|
|
5
5
|
import type { ComputedRef, Ref } from 'vue';
|
|
6
|
+
import type { BlueskyEmbedInput } from './registry/bluesky-embed.js';
|
|
6
7
|
import type { ClarityInput } from './registry/clarity.js';
|
|
7
8
|
import type { CloudflareWebAnalyticsInput } from './registry/cloudflare-web-analytics.js';
|
|
8
9
|
import type { CrispInput } from './registry/crisp.js';
|
|
@@ -155,6 +156,7 @@ export interface NuxtDevToolsScriptInstance {
|
|
|
155
156
|
}[];
|
|
156
157
|
}
|
|
157
158
|
export interface ScriptRegistry {
|
|
159
|
+
blueskyEmbed?: BlueskyEmbedInput;
|
|
158
160
|
crisp?: CrispInput;
|
|
159
161
|
clarity?: ClarityInput;
|
|
160
162
|
cloudflareWebAnalytics?: CloudflareWebAnalyticsInput;
|
|
@@ -214,7 +216,17 @@ export type RegistryScriptInput<T = EmptyOptionsSchema, Bundelable extends boole
|
|
|
214
216
|
scriptInput: Required<Pick<ScriptInput, 'src'>> & ScriptInput;
|
|
215
217
|
scriptOptions?: Omit<NuxtUseScriptOptions, Bundelable extends true ? '' : 'bundle' | Usable extends true ? '' : 'use'>;
|
|
216
218
|
} : never);
|
|
219
|
+
export interface RegistryScriptServerHandler {
|
|
220
|
+
route: string;
|
|
221
|
+
handler: string;
|
|
222
|
+
middleware?: boolean;
|
|
223
|
+
}
|
|
217
224
|
export interface RegistryScript {
|
|
225
|
+
/**
|
|
226
|
+
* The config key used in `scripts.registry` in nuxt.config (e.g., 'googleAnalytics', 'plausibleAnalytics').
|
|
227
|
+
* Used for direct lookup from config to script — avoids fragile import name convention matching.
|
|
228
|
+
*/
|
|
229
|
+
registryKey?: string;
|
|
218
230
|
import?: Import;
|
|
219
231
|
scriptBundling?: false | ((options?: any) => string | false);
|
|
220
232
|
/**
|
|
@@ -235,6 +247,10 @@ export interface RegistryScript {
|
|
|
235
247
|
light: string;
|
|
236
248
|
dark: string;
|
|
237
249
|
};
|
|
250
|
+
/**
|
|
251
|
+
* Server handlers (routes/middleware) to register when this script is enabled via registry config.
|
|
252
|
+
*/
|
|
253
|
+
serverHandlers?: RegistryScriptServerHandler[];
|
|
238
254
|
}
|
|
239
255
|
export type ElementScriptTrigger = 'immediate' | 'visible' | string | string[] | false;
|
|
240
256
|
export type RegistryScripts = RegistryScript[];
|
package/dist/runtime/utils.d.ts
CHANGED
|
@@ -14,5 +14,6 @@ type OptionsFn<O> = (options: InferIfSchema<O>, ctx: {
|
|
|
14
14
|
scriptMode?: 'external' | 'npm';
|
|
15
15
|
});
|
|
16
16
|
export declare function scriptRuntimeConfig<T extends keyof ScriptRegistry>(key: T): ScriptRegistry[T];
|
|
17
|
+
export declare function requireRegistryEndpoint(componentName: string, registryKey: string): void;
|
|
17
18
|
export declare function useRegistryScript<T extends Record<string | symbol, any>, O = EmptyOptionsSchema>(registryKey: keyof ScriptRegistry | string, optionsFn: OptionsFn<O>, _userOptions?: RegistryScriptInput<O>): UseScriptContext<UseFunctionType<NuxtUseScriptOptions<T>, T>>;
|
|
18
19
|
export * from './utils/pure.js';
|
package/dist/runtime/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parse } from "#nuxt-scripts-validator";
|
|
2
2
|
import { defu } from "defu";
|
|
3
|
-
import { useRuntimeConfig } from "nuxt/app";
|
|
3
|
+
import { createError, useRuntimeConfig } from "nuxt/app";
|
|
4
4
|
import { parseQuery, parseURL, withQuery } from "ufo";
|
|
5
5
|
import { useScript } from "./composables/useScript.js";
|
|
6
6
|
import { createNpmScriptStub } from "./npm-script-stub.js";
|
|
@@ -21,6 +21,15 @@ function validateScriptInputSchema(key, schema, options) {
|
|
|
21
21
|
export function scriptRuntimeConfig(key) {
|
|
22
22
|
return (useRuntimeConfig().public.scripts || {})[key];
|
|
23
23
|
}
|
|
24
|
+
export function requireRegistryEndpoint(componentName, registryKey) {
|
|
25
|
+
const endpoints = useRuntimeConfig().public["nuxt-scripts"]?.endpoints;
|
|
26
|
+
if (!endpoints?.[registryKey]) {
|
|
27
|
+
throw createError({
|
|
28
|
+
message: `${componentName} requires \`scripts.registry.${registryKey}\` to be enabled in nuxt.config`,
|
|
29
|
+
fatal: import.meta.dev
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
24
33
|
export function useRegistryScript(registryKey, optionsFn, _userOptions) {
|
|
25
34
|
const scriptConfig = scriptRuntimeConfig(registryKey);
|
|
26
35
|
const userOptions = Object.assign(_userOptions || {}, typeof scriptConfig === "object" ? scriptConfig : {});
|
|
@@ -49,7 +58,7 @@ export function useRegistryScript(registryKey, optionsFn, _userOptions) {
|
|
|
49
58
|
};
|
|
50
59
|
}
|
|
51
60
|
const scriptInput = defu(finalScriptInput, userOptions.scriptInput, { key: registryKey });
|
|
52
|
-
const scriptOptions =
|
|
61
|
+
const scriptOptions = { ...userOptions?.scriptOptions, ...options.scriptOptions };
|
|
53
62
|
if (import.meta.dev) {
|
|
54
63
|
const error = new Error("Stack trace for component location");
|
|
55
64
|
const stack = error.stack?.split("\n");
|
package/dist/stats.mjs
CHANGED
|
@@ -734,7 +734,7 @@ function deriveMetaKey(importName, label) {
|
|
|
734
734
|
async function getScriptStats() {
|
|
735
735
|
const { registry } = await import('./registry.mjs');
|
|
736
736
|
const entries = await registry();
|
|
737
|
-
const proxyConfigs = getAllProxyConfigs("/_scripts");
|
|
737
|
+
const proxyConfigs = getAllProxyConfigs("/_scripts/assets");
|
|
738
738
|
const sizes = scriptSizes;
|
|
739
739
|
return entries.map((entry) => {
|
|
740
740
|
const metaKey = deriveMetaKey(entry.import?.name, entry.label);
|
package/dist/types-source.mjs
CHANGED
|
@@ -193,6 +193,18 @@ const stripe = [
|
|
|
193
193
|
}
|
|
194
194
|
];
|
|
195
195
|
const registryTypes = {
|
|
196
|
+
"bluesky-embed": [
|
|
197
|
+
{
|
|
198
|
+
name: "BlueskyEmbedOptions",
|
|
199
|
+
kind: "const",
|
|
200
|
+
code: "export const BlueskyEmbedOptions = object({\n /**\n * The Bluesky post URL to embed.\n * @example 'https://bsky.app/profile/bsky.app/post/3mgnwwvj3u22a'\n */\n postUrl: string(),\n /**\n * Custom API endpoint for fetching post data.\n * @default '/_scripts/embed/bluesky'\n */\n apiEndpoint: optional(string()),\n /**\n * Custom image proxy endpoint.\n * @default '/_scripts/embed/bluesky-image'\n */\n imageProxyEndpoint: optional(string()),\n})"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: "BlueskyEmbedPostData",
|
|
204
|
+
kind: "interface",
|
|
205
|
+
code: "export interface BlueskyEmbedPostData {\n uri: string\n cid: string\n author: {\n did: string\n handle: string\n displayName: string\n avatar: string\n labels: Array<{ val: string }>\n verification?: {\n verifiedStatus: string\n }\n }\n record: {\n $type: string\n createdAt: string\n text: string\n langs?: string[]\n facets?: Array<{\n features: Array<{\n $type: string\n uri?: string\n did?: string\n tag?: string\n }>\n index: {\n byteStart: number\n byteEnd: number\n }\n }>\n embed?: {\n $type: string\n images?: Array<{\n alt: string\n image: { ref: { $link: string }, mimeType: string, size: number }\n aspectRatio?: { width: number, height: number }\n }>\n external?: {\n uri: string\n title: string\n description: string\n thumb?: { ref: { $link: string }, mimeType: string, size: number }\n }\n }\n }\n embed?: {\n $type: string\n images?: Array<{\n thumb: string\n fullsize: string\n alt: string\n aspectRatio?: { width: number, height: number }\n }>\n external?: {\n uri: string\n title: string\n description: string\n thumb?: string\n }\n }\n likeCount: number\n repostCount: number\n replyCount: number\n quoteCount: number\n indexedAt: string\n labels: Array<{ val: string }>\n}"
|
|
206
|
+
}
|
|
207
|
+
],
|
|
196
208
|
clarity: clarity,
|
|
197
209
|
"cloudflare-web-analytics": [
|
|
198
210
|
{
|
|
@@ -499,17 +511,17 @@ const registryTypes = {
|
|
|
499
511
|
{
|
|
500
512
|
name: "InstagramEmbedOptions",
|
|
501
513
|
kind: "const",
|
|
502
|
-
code: "export const InstagramEmbedOptions = object({\n /**\n * The Instagram post URL to embed.\n * @example 'https://www.instagram.com/p/C_XXXXXXXXX/'\n * @see https://developers.facebook.com/docs/instagram/oembed/\n */\n postUrl: string(),\n /**\n * Whether to include captions in the embed.\n * @default true\n */\n captions: optional(boolean()),\n /**\n * Custom API endpoint for fetching embed HTML.\n * @default '/
|
|
514
|
+
code: "export const InstagramEmbedOptions = object({\n /**\n * The Instagram post URL to embed.\n * @example 'https://www.instagram.com/p/C_XXXXXXXXX/'\n * @see https://developers.facebook.com/docs/instagram/oembed/\n */\n postUrl: string(),\n /**\n * Whether to include captions in the embed.\n * @default true\n */\n captions: optional(boolean()),\n /**\n * Custom API endpoint for fetching embed HTML.\n * @default '/_scripts/embed/instagram'\n */\n apiEndpoint: optional(string()),\n})"
|
|
503
515
|
},
|
|
504
516
|
{
|
|
505
517
|
name: "ScriptInstagramEmbedProps",
|
|
506
518
|
kind: "interface",
|
|
507
|
-
code: "interface ScriptInstagramEmbedProps {\n /**\n * The Instagram post URL to embed\n * e.g., https://www.instagram.com/p/ABC123/\n */\n postUrl: string\n /**\n * Whether to include captions in the embed\n * @default true\n */\n captions?: boolean\n /**\n * Custom API endpoint for fetching embed HTML\n * @default '/
|
|
519
|
+
code: "interface ScriptInstagramEmbedProps {\n /**\n * The Instagram post URL to embed\n * e.g., https://www.instagram.com/p/ABC123/\n */\n postUrl: string\n /**\n * Whether to include captions in the embed\n * @default true\n */\n captions?: boolean\n /**\n * Custom API endpoint for fetching embed HTML\n * @default '/_scripts/embed/instagram'\n */\n apiEndpoint?: string\n /**\n * Root element attributes\n */\n rootAttrs?: HTMLAttributes\n}"
|
|
508
520
|
},
|
|
509
521
|
{
|
|
510
522
|
name: "ScriptInstagramEmbedDefaults",
|
|
511
523
|
kind: "const",
|
|
512
|
-
code: "const ScriptInstagramEmbedDefaults = {\n \"captions\": \"true\",\n \"apiEndpoint\": \"'/
|
|
524
|
+
code: "const ScriptInstagramEmbedDefaults = {\n \"captions\": \"true\",\n \"apiEndpoint\": \"'/_scripts/embed/instagram'\"\n}"
|
|
513
525
|
}
|
|
514
526
|
],
|
|
515
527
|
intercom: intercom,
|
|
@@ -789,7 +801,7 @@ const registryTypes = {
|
|
|
789
801
|
{
|
|
790
802
|
name: "XEmbedOptions",
|
|
791
803
|
kind: "const",
|
|
792
|
-
code: "export const XEmbedOptions = object({\n /**\n * The tweet ID to embed.\n * @example '1754336034228171055'\n * @see https://developer.x.com/en/docs/twitter-for-websites/embedded-tweets/overview\n */\n tweetId: string(),\n /**\n * Optional: Custom API endpoint for fetching tweet data.\n * @default '/
|
|
804
|
+
code: "export const XEmbedOptions = object({\n /**\n * The tweet ID to embed.\n * @example '1754336034228171055'\n * @see https://developer.x.com/en/docs/twitter-for-websites/embedded-tweets/overview\n */\n tweetId: string(),\n /**\n * Optional: Custom API endpoint for fetching tweet data.\n * @default '/_scripts/embed/x'\n */\n apiEndpoint: optional(string()),\n /**\n * Optional: Custom image proxy endpoint.\n * @default '/_scripts/embed/x-image'\n */\n imageProxyEndpoint: optional(string()),\n})"
|
|
793
805
|
},
|
|
794
806
|
{
|
|
795
807
|
name: "XEmbedTweetData",
|
|
@@ -799,12 +811,12 @@ const registryTypes = {
|
|
|
799
811
|
{
|
|
800
812
|
name: "ScriptXEmbedProps",
|
|
801
813
|
kind: "interface",
|
|
802
|
-
code: "interface ScriptXEmbedProps {\n /**\n * The tweet ID to embed\n */\n tweetId: string\n /**\n * Custom API endpoint for fetching tweet data\n * @default '/
|
|
814
|
+
code: "interface ScriptXEmbedProps {\n /**\n * The tweet ID to embed\n */\n tweetId: string\n /**\n * Custom API endpoint for fetching tweet data\n * @default '/_scripts/embed/x'\n */\n apiEndpoint?: string\n /**\n * Custom image proxy endpoint\n * @default '/_scripts/embed/x-image'\n */\n imageProxyEndpoint?: string\n /**\n * Root element attributes\n */\n rootAttrs?: HTMLAttributes\n}"
|
|
803
815
|
},
|
|
804
816
|
{
|
|
805
817
|
name: "ScriptXEmbedDefaults",
|
|
806
818
|
kind: "const",
|
|
807
|
-
code: "const ScriptXEmbedDefaults = {\n \"apiEndpoint\": \"'/
|
|
819
|
+
code: "const ScriptXEmbedDefaults = {\n \"apiEndpoint\": \"'/_scripts/embed/x'\",\n \"imageProxyEndpoint\": \"'/_scripts/embed/x-image'\"\n}"
|
|
808
820
|
}
|
|
809
821
|
],
|
|
810
822
|
"x-pixel": [
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuxt/scripts",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0-beta.
|
|
4
|
+
"version": "1.0.0-beta.22",
|
|
5
5
|
"description": "Load third-party scripts with better performance, privacy and DX in Nuxt Apps.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
},
|
|
98
98
|
"dependencies": {
|
|
99
99
|
"@nuxt/devtools-kit": "^3.2.3",
|
|
100
|
-
"@nuxt/kit": "^4.
|
|
100
|
+
"@nuxt/kit": "^4.4.2",
|
|
101
101
|
"@vueuse/core": "^14.2.1",
|
|
102
102
|
"consola": "^3.4.2",
|
|
103
103
|
"defu": "^6.1.4",
|
|
@@ -105,19 +105,20 @@
|
|
|
105
105
|
"magic-string": "^0.30.21",
|
|
106
106
|
"ofetch": "^1.5.1",
|
|
107
107
|
"ohash": "^2.0.11",
|
|
108
|
-
"oxc-parser": "^0.
|
|
108
|
+
"oxc-parser": "^0.118.0",
|
|
109
109
|
"oxc-walker": "^0.7.0",
|
|
110
110
|
"pathe": "^2.0.3",
|
|
111
111
|
"pkg-types": "^2.3.0",
|
|
112
112
|
"sirv": "^3.0.2",
|
|
113
113
|
"std-env": "^4.0.0",
|
|
114
114
|
"ufo": "^1.6.3",
|
|
115
|
+
"ultrahtml": "^1.6.0",
|
|
115
116
|
"unplugin": "^3.0.0",
|
|
116
117
|
"unstorage": "^1.17.4",
|
|
117
118
|
"valibot": "^1.2.0"
|
|
118
119
|
},
|
|
119
120
|
"devDependencies": {
|
|
120
|
-
"@antfu/eslint-config": "^7.7.
|
|
121
|
+
"@antfu/eslint-config": "^7.7.2",
|
|
121
122
|
"@nuxt/devtools-ui-kit": "^3.2.3",
|
|
122
123
|
"@nuxt/module-builder": "^1.0.2",
|
|
123
124
|
"@nuxt/test-utils": "^4.0.0",
|
|
@@ -130,18 +131,18 @@
|
|
|
130
131
|
"bumpp": "^10.4.1",
|
|
131
132
|
"eslint": "^10.0.3",
|
|
132
133
|
"eslint-plugin-harlanzw": "^0.5.17",
|
|
133
|
-
"happy-dom": "^20.8.
|
|
134
|
+
"happy-dom": "^20.8.4",
|
|
134
135
|
"knitwork": "^1.3.0",
|
|
135
|
-
"nuxt": "^4.
|
|
136
|
+
"nuxt": "^4.4.2",
|
|
136
137
|
"playwright-core": "^1.58.2",
|
|
137
|
-
"posthog-js": "^1.360.
|
|
138
|
+
"posthog-js": "^1.360.1",
|
|
138
139
|
"shiki": "^4.0.2",
|
|
139
140
|
"typescript": "^5.9.3",
|
|
140
|
-
"vitest": "^4.0
|
|
141
|
+
"vitest": "^4.1.0",
|
|
141
142
|
"vue": "^3.5.30",
|
|
142
143
|
"vue-router": "^5.0.3",
|
|
143
144
|
"vue-tsc": "^3.2.5",
|
|
144
|
-
"@nuxt/scripts": "1.0.0-beta.
|
|
145
|
+
"@nuxt/scripts": "1.0.0-beta.22"
|
|
145
146
|
},
|
|
146
147
|
"resolutions": {
|
|
147
148
|
"@nuxt/scripts": "workspace:*"
|