@nuxt/scripts 1.0.0-rc.9 → 1.0.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.
- package/dist/devtools-client/200.html +1 -1
- package/dist/devtools-client/404.html +1 -1
- package/dist/devtools-client/_nuxt/{BBS9G2Kb.js → Br5kvbNb.js} +1 -1
- package/dist/devtools-client/_nuxt/C25MBdR1.js +1 -0
- package/dist/devtools-client/_nuxt/{DCBsJT4N.js → Cg_OIb5q.js} +1 -1
- package/dist/devtools-client/_nuxt/{B4uHpJPz.js → D2o5loaz.js} +1 -1
- package/dist/devtools-client/_nuxt/De7Wf2b9.js +188 -0
- package/dist/devtools-client/_nuxt/{Cxq4HLPL.js → DnVCfhVR.js} +1 -1
- package/dist/devtools-client/_nuxt/builds/latest.json +1 -1
- package/dist/devtools-client/_nuxt/builds/meta/9d868e70-bc5a-425c-8c84-8defe5186920.json +1 -0
- package/dist/devtools-client/_nuxt/{entry.BwpOBArY.css → entry.BSxy0W1q.css} +1 -1
- package/dist/devtools-client/_nuxt/index.DZD1lwyI.css +1 -0
- package/dist/devtools-client/_nuxt/{DvZScWzI.js → pN4-T8ZD.js} +1 -1
- package/dist/devtools-client/docs/index.html +1 -1
- package/dist/devtools-client/first-party/index.html +1 -1
- package/dist/devtools-client/index.html +1 -1
- package/dist/devtools-client/registry/index.html +1 -1
- package/dist/module.d.mts +15 -0
- package/dist/module.d.ts +15 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +35 -8
- package/dist/registry.mjs +3 -3
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.vue +6 -2
- package/dist/runtime/components/ScriptBlueskyEmbed.d.vue.ts +0 -1
- package/dist/runtime/components/ScriptBlueskyEmbed.vue +12 -10
- package/dist/runtime/components/ScriptBlueskyEmbed.vue.d.ts +0 -1
- package/dist/runtime/components/ScriptInstagramEmbed.vue +3 -1
- package/dist/runtime/components/ScriptXEmbed.d.vue.ts +0 -1
- package/dist/runtime/components/ScriptXEmbed.vue +11 -9
- package/dist/runtime/components/ScriptXEmbed.vue.d.ts +0 -1
- package/dist/runtime/composables/useScript.js +17 -6
- package/dist/runtime/composables/useScriptProxyToken.d.ts +12 -0
- package/dist/runtime/composables/useScriptProxyToken.js +4 -0
- package/dist/runtime/composables/useScriptProxyUrl.d.ts +12 -0
- package/dist/runtime/composables/useScriptProxyUrl.js +27 -0
- package/dist/runtime/plugins/proxy-token.server.d.ts +10 -0
- package/dist/runtime/plugins/proxy-token.server.js +17 -0
- package/dist/runtime/registry/bing-uet.d.ts +6 -2
- package/dist/runtime/registry/bing-uet.js +13 -1
- package/dist/runtime/registry/bluesky-embed.d.ts +0 -4
- package/dist/runtime/registry/bluesky-embed.js +0 -4
- package/dist/runtime/registry/clarity.d.ts +6 -2
- package/dist/runtime/registry/clarity.js +12 -1
- package/dist/runtime/registry/google-analytics.d.ts +6 -2
- package/dist/runtime/registry/google-analytics.js +12 -1
- package/dist/runtime/registry/google-tag-manager.d.ts +6 -2
- package/dist/runtime/registry/google-tag-manager.js +10 -1
- package/dist/runtime/registry/gravatar.js +10 -13
- package/dist/runtime/registry/matomo-analytics.d.ts +9 -3
- package/dist/runtime/registry/matomo-analytics.js +28 -1
- package/dist/runtime/registry/meta-pixel.d.ts +8 -2
- package/dist/runtime/registry/meta-pixel.js +10 -1
- package/dist/runtime/registry/mixpanel-analytics.d.ts +12 -2
- package/dist/runtime/registry/mixpanel-analytics.js +16 -4
- package/dist/runtime/registry/posthog.d.ts +8 -2
- package/dist/runtime/registry/posthog.js +15 -4
- package/dist/runtime/registry/schemas.d.ts +65 -0
- package/dist/runtime/registry/schemas.js +75 -8
- package/dist/runtime/registry/tiktok-pixel.d.ts +16 -2
- package/dist/runtime/registry/tiktok-pixel.js +22 -1
- package/dist/runtime/registry/x-embed.d.ts +0 -4
- package/dist/runtime/registry/x-embed.js +0 -4
- package/dist/runtime/server/bluesky-embed-image.d.ts +1 -1
- package/dist/runtime/server/bluesky-embed.d.ts +1 -15
- package/dist/runtime/server/bluesky-embed.js +22 -4
- package/dist/runtime/server/google-maps-geocode-proxy.js +8 -5
- package/dist/runtime/server/google-static-maps-proxy.d.ts +1 -1
- package/dist/runtime/server/google-static-maps-proxy.js +13 -8
- package/dist/runtime/server/gravatar-proxy.d.ts +1 -1
- package/dist/runtime/server/gravatar-proxy.js +6 -7
- package/dist/runtime/server/instagram-embed-asset.d.ts +1 -1
- package/dist/runtime/server/instagram-embed-image.d.ts +1 -1
- package/dist/runtime/server/instagram-embed.js +22 -10
- package/dist/runtime/server/utils/cached-upstream.d.ts +55 -0
- package/dist/runtime/server/utils/cached-upstream.js +65 -0
- package/dist/runtime/server/utils/embed-rewriters.d.ts +19 -0
- package/dist/runtime/server/utils/embed-rewriters.js +41 -0
- package/dist/runtime/server/utils/image-proxy.d.ts +3 -1
- package/dist/runtime/server/utils/image-proxy.js +8 -6
- package/dist/runtime/server/utils/instagram-embed.d.ts +4 -4
- package/dist/runtime/server/utils/instagram-embed.js +10 -9
- package/dist/runtime/server/utils/proxy-url.d.ts +9 -0
- package/dist/runtime/server/utils/proxy-url.js +21 -0
- package/dist/runtime/server/utils/sign-constants.d.ts +16 -0
- package/dist/runtime/server/utils/sign-constants.js +5 -0
- package/dist/runtime/server/utils/sign.d.ts +2 -10
- package/dist/runtime/server/utils/sign.js +8 -5
- package/dist/runtime/server/utils/withSigning.js +3 -2
- package/dist/runtime/server/x-embed-image.d.ts +1 -1
- package/dist/runtime/server/x-embed.js +20 -2
- package/dist/runtime/types.d.ts +24 -1
- package/dist/types-source.mjs +104 -11
- package/package.json +2 -2
- package/dist/devtools-client/_nuxt/CQR4zIAm.js +0 -1
- package/dist/devtools-client/_nuxt/DTxy5P8N.js +0 -188
- package/dist/devtools-client/_nuxt/builds/meta/bd58b869-1eb5-4c50-871c-707f9b71e8f9.json +0 -1
- package/dist/devtools-client/_nuxt/index.CA-OpSj0.css +0 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { defineCachedFunction } from "nitropack/runtime";
|
|
3
|
+
import { $fetch } from "ofetch";
|
|
4
|
+
export function createCachedBinaryFetch(name, maxAge) {
|
|
5
|
+
const cached = defineCachedFunction(
|
|
6
|
+
async (url, opts) => {
|
|
7
|
+
const response = await $fetch.raw(url, {
|
|
8
|
+
responseType: "arrayBuffer",
|
|
9
|
+
timeout: opts?.timeout ?? 1e4,
|
|
10
|
+
redirect: opts?.redirect ?? "follow",
|
|
11
|
+
ignoreResponseError: opts?.ignoreResponseError ?? false,
|
|
12
|
+
headers: opts?.headers
|
|
13
|
+
});
|
|
14
|
+
const data = response._data;
|
|
15
|
+
return {
|
|
16
|
+
base64: data ? Buffer.from(data).toString("base64") : "",
|
|
17
|
+
contentType: response.headers.get("content-type"),
|
|
18
|
+
status: response.status
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name,
|
|
23
|
+
maxAge,
|
|
24
|
+
swr: true,
|
|
25
|
+
staleMaxAge: maxAge,
|
|
26
|
+
getKey: (url, opts) => {
|
|
27
|
+
if (!opts)
|
|
28
|
+
return url;
|
|
29
|
+
const parts = [url];
|
|
30
|
+
if (opts.headers) {
|
|
31
|
+
const entries = Object.entries(opts.headers).sort(([a], [b]) => a.localeCompare(b));
|
|
32
|
+
for (const [k, v] of entries)
|
|
33
|
+
parts.push(`${k}=${v}`);
|
|
34
|
+
}
|
|
35
|
+
if (opts.redirect)
|
|
36
|
+
parts.push(`redirect=${opts.redirect}`);
|
|
37
|
+
return parts.join("|");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
return async (url, opts) => {
|
|
42
|
+
const result = await cached(url, opts);
|
|
43
|
+
return {
|
|
44
|
+
...result,
|
|
45
|
+
body: result.base64 ? Buffer.from(result.base64, "base64") : Buffer.alloc(0)
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function createCachedJsonFetch(name, maxAge, getKey) {
|
|
50
|
+
return defineCachedFunction(
|
|
51
|
+
async (url, opts) => {
|
|
52
|
+
return await $fetch(url, {
|
|
53
|
+
timeout: opts?.timeout ?? 1e4,
|
|
54
|
+
headers: opts?.headers
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name,
|
|
59
|
+
maxAge,
|
|
60
|
+
swr: true,
|
|
61
|
+
staleMaxAge: maxAge,
|
|
62
|
+
getKey
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutate a tweet (and any quoted tweet) in place so every raw CDN image URL
|
|
3
|
+
* is rewritten to route through the site's `/embed/x-image` proxy. When a
|
|
4
|
+
* `secret` is provided, URLs are HMAC-signed and pass `withSigning` without a
|
|
5
|
+
* page token.
|
|
6
|
+
*
|
|
7
|
+
* Clone the input first if it came from a shared cache — this function does
|
|
8
|
+
* not copy.
|
|
9
|
+
*/
|
|
10
|
+
export declare function rewriteTweetImages(tweet: any, imagePath: string, secret?: string): void;
|
|
11
|
+
/**
|
|
12
|
+
* Mutate a Bluesky post in place so every CDN image URL routes through the
|
|
13
|
+
* site's `/embed/bluesky-image` proxy. Covers author avatar, embedded images
|
|
14
|
+
* (thumb + fullsize), and external embed thumbnails.
|
|
15
|
+
*
|
|
16
|
+
* Clone the input first if it came from a shared cache — this function does
|
|
17
|
+
* not copy.
|
|
18
|
+
*/
|
|
19
|
+
export declare function rewriteBlueskyPostImages(post: any, imagePath: string, secret?: string): void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { buildProxyUrl } from "./proxy-url.js";
|
|
2
|
+
export function rewriteTweetImages(tweet, imagePath, secret) {
|
|
3
|
+
if (!tweet)
|
|
4
|
+
return;
|
|
5
|
+
if (tweet.user?.profile_image_url_https)
|
|
6
|
+
tweet.user.profile_image_url_https = buildProxyUrl(imagePath, { url: tweet.user.profile_image_url_https }, secret);
|
|
7
|
+
if (tweet.photos) {
|
|
8
|
+
for (const photo of tweet.photos) {
|
|
9
|
+
if (photo.url)
|
|
10
|
+
photo.url = buildProxyUrl(imagePath, { url: photo.url }, secret);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
if (tweet.entities?.media) {
|
|
14
|
+
for (const media of tweet.entities.media) {
|
|
15
|
+
if (media.media_url_https)
|
|
16
|
+
media.media_url_https = buildProxyUrl(imagePath, { url: media.media_url_https }, secret);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (tweet.video?.poster)
|
|
20
|
+
tweet.video.poster = buildProxyUrl(imagePath, { url: tweet.video.poster }, secret);
|
|
21
|
+
if (tweet.quoted_tweet)
|
|
22
|
+
rewriteTweetImages(tweet.quoted_tweet, imagePath, secret);
|
|
23
|
+
}
|
|
24
|
+
export function rewriteBlueskyPostImages(post, imagePath, secret) {
|
|
25
|
+
if (!post)
|
|
26
|
+
return;
|
|
27
|
+
const proxy = (url) => url ? buildProxyUrl(imagePath, { url }, secret) : url;
|
|
28
|
+
if (post.author?.avatar)
|
|
29
|
+
post.author.avatar = proxy(post.author.avatar);
|
|
30
|
+
const embed = post.embed;
|
|
31
|
+
if (embed?.images) {
|
|
32
|
+
for (const image of embed.images) {
|
|
33
|
+
if (image.thumb)
|
|
34
|
+
image.thumb = proxy(image.thumb);
|
|
35
|
+
if (image.fullsize)
|
|
36
|
+
image.fullsize = proxy(image.fullsize);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (embed?.external?.thumb)
|
|
40
|
+
embed.external.thumb = proxy(embed.external.thumb);
|
|
41
|
+
}
|
|
@@ -8,5 +8,7 @@ export interface ImageProxyConfig {
|
|
|
8
8
|
followRedirects?: boolean;
|
|
9
9
|
/** Decode & in URL query parameter */
|
|
10
10
|
decodeAmpersands?: boolean;
|
|
11
|
+
/** Unique name for the nitro cache group (defaults to derived from allowedDomains). */
|
|
12
|
+
cacheName?: string;
|
|
11
13
|
}
|
|
12
|
-
export declare function createImageProxyHandler(config: ImageProxyConfig): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<
|
|
14
|
+
export declare function createImageProxyHandler(config: ImageProxyConfig): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<Buffer<ArrayBufferLike>>>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createError, defineEventHandler, getQuery, setHeader } from "h3";
|
|
2
|
-
import {
|
|
2
|
+
import { createCachedBinaryFetch } from "./cached-upstream.js";
|
|
3
3
|
import { withSigning } from "./withSigning.js";
|
|
4
4
|
const AMP_RE = /&/g;
|
|
5
5
|
export function createImageProxyHandler(config) {
|
|
@@ -9,8 +9,10 @@ export function createImageProxyHandler(config) {
|
|
|
9
9
|
cacheMaxAge = 3600,
|
|
10
10
|
contentType = "image/jpeg",
|
|
11
11
|
followRedirects = true,
|
|
12
|
-
decodeAmpersands = false
|
|
12
|
+
decodeAmpersands = false,
|
|
13
|
+
cacheName = Array.isArray(config.allowedDomains) ? `nuxt-scripts-img:${config.allowedDomains[0] || "default"}` : "nuxt-scripts-img:custom"
|
|
13
14
|
} = config;
|
|
15
|
+
const cachedFetch = createCachedBinaryFetch(cacheName, cacheMaxAge);
|
|
14
16
|
return withSigning(defineEventHandler(async (event) => {
|
|
15
17
|
const query = getQuery(event);
|
|
16
18
|
let url = query.url;
|
|
@@ -47,7 +49,7 @@ export function createImageProxyHandler(config) {
|
|
|
47
49
|
const headers = { Accept: accept };
|
|
48
50
|
if (userAgent)
|
|
49
51
|
headers["User-Agent"] = userAgent;
|
|
50
|
-
const
|
|
52
|
+
const result = await cachedFetch(url, {
|
|
51
53
|
timeout: 5e3,
|
|
52
54
|
redirect: followRedirects ? "follow" : "manual",
|
|
53
55
|
ignoreResponseError: !followRedirects,
|
|
@@ -58,14 +60,14 @@ export function createImageProxyHandler(config) {
|
|
|
58
60
|
statusMessage: error.statusMessage || "Failed to fetch image"
|
|
59
61
|
});
|
|
60
62
|
});
|
|
61
|
-
if (!followRedirects &&
|
|
63
|
+
if (!followRedirects && result.status >= 300 && result.status < 400) {
|
|
62
64
|
throw createError({
|
|
63
65
|
statusCode: 403,
|
|
64
66
|
statusMessage: "Redirects not allowed"
|
|
65
67
|
});
|
|
66
68
|
}
|
|
67
|
-
setHeader(event, "Content-Type",
|
|
69
|
+
setHeader(event, "Content-Type", result.contentType || contentType);
|
|
68
70
|
setHeader(event, "Cache-Control", `public, max-age=${cacheMaxAge}, s-maxage=${cacheMaxAge}`);
|
|
69
|
-
return
|
|
71
|
+
return result.body;
|
|
70
72
|
}));
|
|
71
73
|
}
|
|
@@ -5,10 +5,10 @@ export declare const STATIC_CDN_RE: RegExp;
|
|
|
5
5
|
export declare const LOOKASIDE_RE: RegExp;
|
|
6
6
|
export declare const INSTAGRAM_IMAGE_HOSTS: string[];
|
|
7
7
|
export declare const INSTAGRAM_ASSET_HOST = "static.cdninstagram.com";
|
|
8
|
-
export declare function proxyImageUrl(url: string, prefix?: string): string;
|
|
9
|
-
export declare function proxyAssetUrl(url: string, prefix?: string): string;
|
|
10
|
-
export declare function rewriteUrl(url: string, prefix?: string): string;
|
|
11
|
-
export declare function rewriteUrlsInText(text: string, prefix?: string): string;
|
|
8
|
+
export declare function proxyImageUrl(url: string, prefix?: string, secret?: string): string;
|
|
9
|
+
export declare function proxyAssetUrl(url: string, prefix?: string, secret?: string): string;
|
|
10
|
+
export declare function rewriteUrl(url: string, prefix?: string, secret?: string): string;
|
|
11
|
+
export declare function rewriteUrlsInText(text: string, prefix?: string, secret?: string): string;
|
|
12
12
|
/**
|
|
13
13
|
* Scope CSS rules under a parent selector and strip global/page-level rules.
|
|
14
14
|
* Removes :root, html, body selectors and @charset/@import at-rules.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildProxyUrl } from "./proxy-url.js";
|
|
1
2
|
export const RSRC_RE = /url\(\/rsrc\.php([^)]+)\)/g;
|
|
2
3
|
export const AMP_RE = /&/g;
|
|
3
4
|
export const SCONTENT_RE = /https:\/\/scontent[^"'\s),]+\.cdninstagram\.com[^"'\s),]+/g;
|
|
@@ -10,25 +11,25 @@ const IMPORT_RE = /@import\s[^;]+;/gi;
|
|
|
10
11
|
const WHITESPACE_RE = /\s/;
|
|
11
12
|
const AT_RULE_NAME_RE = /@([\w-]+)/;
|
|
12
13
|
const MULTI_SPACE_RE = /\s+/g;
|
|
13
|
-
export function proxyImageUrl(url, prefix = "/_scripts") {
|
|
14
|
-
return `${prefix}/embed/instagram-image
|
|
14
|
+
export function proxyImageUrl(url, prefix = "/_scripts", secret) {
|
|
15
|
+
return buildProxyUrl(`${prefix}/embed/instagram-image`, { url: url.replace(AMP_RE, "&") }, secret);
|
|
15
16
|
}
|
|
16
|
-
export function proxyAssetUrl(url, prefix = "/_scripts") {
|
|
17
|
-
return `${prefix}/embed/instagram-asset
|
|
17
|
+
export function proxyAssetUrl(url, prefix = "/_scripts", secret) {
|
|
18
|
+
return buildProxyUrl(`${prefix}/embed/instagram-asset`, { url: url.replace(AMP_RE, "&") }, secret);
|
|
18
19
|
}
|
|
19
|
-
export function rewriteUrl(url, prefix = "/_scripts") {
|
|
20
|
+
export function rewriteUrl(url, prefix = "/_scripts", secret) {
|
|
20
21
|
try {
|
|
21
22
|
const parsed = new URL(url);
|
|
22
23
|
if (parsed.hostname === INSTAGRAM_ASSET_HOST)
|
|
23
|
-
return proxyAssetUrl(url, prefix);
|
|
24
|
+
return proxyAssetUrl(url, prefix, secret);
|
|
24
25
|
if (INSTAGRAM_IMAGE_HOSTS.some((h) => parsed.hostname === h || parsed.hostname.endsWith(`.cdninstagram.com`)))
|
|
25
|
-
return proxyImageUrl(url, prefix);
|
|
26
|
+
return proxyImageUrl(url, prefix, secret);
|
|
26
27
|
} catch {
|
|
27
28
|
}
|
|
28
29
|
return url;
|
|
29
30
|
}
|
|
30
|
-
export function rewriteUrlsInText(text, prefix = "/_scripts") {
|
|
31
|
-
return text.replace(SCONTENT_RE, (m) => proxyImageUrl(m, prefix)).replace(STATIC_CDN_RE, (m) => proxyAssetUrl(m, prefix)).replace(LOOKASIDE_RE, (m) => proxyImageUrl(m, prefix));
|
|
31
|
+
export function rewriteUrlsInText(text, prefix = "/_scripts", secret) {
|
|
32
|
+
return text.replace(SCONTENT_RE, (m) => proxyImageUrl(m, prefix, secret)).replace(STATIC_CDN_RE, (m) => proxyAssetUrl(m, prefix, secret)).replace(LOOKASIDE_RE, (m) => proxyImageUrl(m, prefix, secret));
|
|
32
33
|
}
|
|
33
34
|
export function scopeCss(css, scopeSelector) {
|
|
34
35
|
let result = css.replace(CHARSET_RE, "");
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a proxy URL with query params, signing it when a secret is available.
|
|
3
|
+
*
|
|
4
|
+
* Used by embed handlers that inject proxy URLs into HTML/JSON responses.
|
|
5
|
+
* When `secret` is set, URLs are HMAC-signed so clients can fetch them without
|
|
6
|
+
* needing a page token. When it's undefined, URLs fall back to unsigned form
|
|
7
|
+
* (which is only safe when the `withSigning` middleware has no secret either).
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildProxyUrl(path: string, query: Record<string, unknown>, secret?: string): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { buildSignedProxyUrl } from "./sign.js";
|
|
2
|
+
export function buildProxyUrl(path, query, secret) {
|
|
3
|
+
if (secret)
|
|
4
|
+
return buildSignedProxyUrl(path, query, secret);
|
|
5
|
+
const parts = [];
|
|
6
|
+
for (const [key, value] of Object.entries(query)) {
|
|
7
|
+
if (value === void 0 || value === null)
|
|
8
|
+
continue;
|
|
9
|
+
const encodedKey = encodeURIComponent(key);
|
|
10
|
+
if (Array.isArray(value)) {
|
|
11
|
+
for (const item of value) {
|
|
12
|
+
if (item === void 0 || item === null)
|
|
13
|
+
continue;
|
|
14
|
+
parts.push(`${encodedKey}=${encodeURIComponent(String(item))}`);
|
|
15
|
+
}
|
|
16
|
+
} else {
|
|
17
|
+
parts.push(`${encodedKey}=${encodeURIComponent(String(value))}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return parts.length ? `${path}?${parts.join("&")}` : path;
|
|
21
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signing constants shared between server (HMAC) and client (page-token) code.
|
|
3
|
+
*
|
|
4
|
+
* Kept in a crypto-free module so client bundles can import the param names
|
|
5
|
+
* without pulling in `node:crypto`.
|
|
6
|
+
*/
|
|
7
|
+
/** Query param name for the HMAC signature. */
|
|
8
|
+
export declare const SIG_PARAM = "sig";
|
|
9
|
+
/** Length of the hex signature (16 chars = 64 bits). */
|
|
10
|
+
export declare const SIG_LENGTH = 16;
|
|
11
|
+
/** Query param name for the page token. */
|
|
12
|
+
export declare const PAGE_TOKEN_PARAM = "_pt";
|
|
13
|
+
/** Query param name for the page token timestamp. */
|
|
14
|
+
export declare const PAGE_TOKEN_TS_PARAM = "_ts";
|
|
15
|
+
/** Default max age for page tokens in seconds (1 hour). */
|
|
16
|
+
export declare const PAGE_TOKEN_MAX_AGE = 3600;
|
|
@@ -22,10 +22,8 @@
|
|
|
22
22
|
* prerendered HTML for no practical gain.
|
|
23
23
|
*/
|
|
24
24
|
import type { H3Event } from 'h3';
|
|
25
|
-
|
|
26
|
-
export
|
|
27
|
-
/** Length of the hex signature (16 chars = 64 bits). */
|
|
28
|
-
export declare const SIG_LENGTH = 16;
|
|
25
|
+
import { PAGE_TOKEN_MAX_AGE, PAGE_TOKEN_PARAM, PAGE_TOKEN_TS_PARAM, SIG_LENGTH, SIG_PARAM } from './sign-constants.js';
|
|
26
|
+
export { PAGE_TOKEN_MAX_AGE, PAGE_TOKEN_PARAM, PAGE_TOKEN_TS_PARAM, SIG_LENGTH, SIG_PARAM };
|
|
29
27
|
/**
|
|
30
28
|
* Canonicalize a query object into a deterministic string suitable for HMAC input.
|
|
31
29
|
*
|
|
@@ -61,12 +59,6 @@ export declare function signProxyUrl(path: string, query: Record<string, unknown
|
|
|
61
59
|
* (SSR components, server-side URL rewriters like instagram-embed).
|
|
62
60
|
*/
|
|
63
61
|
export declare function buildSignedProxyUrl(path: string, query: Record<string, unknown>, secret: string): string;
|
|
64
|
-
/** Query param name for the page token. */
|
|
65
|
-
export declare const PAGE_TOKEN_PARAM = "_pt";
|
|
66
|
-
/** Query param name for the page token timestamp. */
|
|
67
|
-
export declare const PAGE_TOKEN_TS_PARAM = "_ts";
|
|
68
|
-
/** Default max age for page tokens in seconds (1 hour). */
|
|
69
|
-
export declare const PAGE_TOKEN_MAX_AGE = 3600;
|
|
70
62
|
/**
|
|
71
63
|
* Generate a page token that authorizes client-side proxy requests.
|
|
72
64
|
*
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { createHmac } from "node:crypto";
|
|
2
2
|
import { getQuery } from "h3";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
PAGE_TOKEN_MAX_AGE,
|
|
5
|
+
PAGE_TOKEN_PARAM,
|
|
6
|
+
PAGE_TOKEN_TS_PARAM,
|
|
7
|
+
SIG_LENGTH,
|
|
8
|
+
SIG_PARAM
|
|
9
|
+
} from "./sign-constants.js";
|
|
10
|
+
export { PAGE_TOKEN_MAX_AGE, PAGE_TOKEN_PARAM, PAGE_TOKEN_TS_PARAM, SIG_LENGTH, SIG_PARAM };
|
|
5
11
|
export function canonicalizeQuery(query) {
|
|
6
12
|
const keys = Object.keys(query).filter((k) => k !== SIG_PARAM && query[k] !== void 0 && query[k] !== null).sort();
|
|
7
13
|
const parts = [];
|
|
@@ -38,9 +44,6 @@ export function buildSignedProxyUrl(path, query, secret) {
|
|
|
38
44
|
const queryString = canonical ? `${canonical}&${SIG_PARAM}=${sig}` : `${SIG_PARAM}=${sig}`;
|
|
39
45
|
return `${path}?${queryString}`;
|
|
40
46
|
}
|
|
41
|
-
export const PAGE_TOKEN_PARAM = "_pt";
|
|
42
|
-
export const PAGE_TOKEN_TS_PARAM = "_ts";
|
|
43
|
-
export const PAGE_TOKEN_MAX_AGE = 3600;
|
|
44
47
|
export function generateProxyToken(secret, timestamp) {
|
|
45
48
|
return createHmac("sha256", secret).update(`proxy-access:${timestamp}`).digest("hex").slice(0, SIG_LENGTH);
|
|
46
49
|
}
|
|
@@ -4,10 +4,11 @@ import { verifyProxyRequest } from "./sign.js";
|
|
|
4
4
|
export function withSigning(handler) {
|
|
5
5
|
return defineEventHandler(async (event) => {
|
|
6
6
|
const runtimeConfig = useRuntimeConfig(event);
|
|
7
|
-
const
|
|
7
|
+
const scriptsConfig = runtimeConfig["nuxt-scripts"];
|
|
8
|
+
const secret = scriptsConfig?.proxySecret;
|
|
8
9
|
if (!secret)
|
|
9
10
|
return handler(event);
|
|
10
|
-
if (!verifyProxyRequest(event, secret)) {
|
|
11
|
+
if (!verifyProxyRequest(event, secret, scriptsConfig?.pageTokenMaxAge)) {
|
|
11
12
|
throw createError({
|
|
12
13
|
statusCode: 403,
|
|
13
14
|
statusMessage: "Invalid signature"
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<
|
|
1
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<Buffer<ArrayBufferLike>>>;
|
|
2
2
|
export default _default;
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { createError, defineEventHandler, getQuery, setHeader } from "h3";
|
|
2
|
-
import {
|
|
2
|
+
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
|
+
import { createCachedJsonFetch } from "./utils/cached-upstream.js";
|
|
4
|
+
import { rewriteTweetImages } from "./utils/embed-rewriters.js";
|
|
3
5
|
import { withSigning } from "./utils/withSigning.js";
|
|
4
6
|
const TWEET_ID_RE = /^\d+$/;
|
|
7
|
+
const EMBED_X_SUFFIX_RE = /\/embed\/x$/;
|
|
8
|
+
const TWEET_ID_FROM_URL_RE = /[?&]id=(\d+)/;
|
|
9
|
+
const cachedTweetFetch = createCachedJsonFetch(
|
|
10
|
+
"nuxt-scripts-x-tweet",
|
|
11
|
+
600,
|
|
12
|
+
(url) => {
|
|
13
|
+
const match = url.match(TWEET_ID_FROM_URL_RE);
|
|
14
|
+
return match?.[1] || url;
|
|
15
|
+
}
|
|
16
|
+
);
|
|
5
17
|
export default withSigning(defineEventHandler(async (event) => {
|
|
6
18
|
const query = getQuery(event);
|
|
7
19
|
const tweetId = query.id;
|
|
@@ -13,7 +25,7 @@ export default withSigning(defineEventHandler(async (event) => {
|
|
|
13
25
|
}
|
|
14
26
|
const randomToken = Array.from(Array.from({ length: 11 }), () => (Math.random() * 36).toString(36)[2]).join("");
|
|
15
27
|
const params = new URLSearchParams({ id: tweetId, token: randomToken });
|
|
16
|
-
const
|
|
28
|
+
const tweetRaw = await cachedTweetFetch(
|
|
17
29
|
`https://cdn.syndication.twimg.com/tweet-result?${params.toString()}`,
|
|
18
30
|
{
|
|
19
31
|
headers: {
|
|
@@ -27,6 +39,12 @@ export default withSigning(defineEventHandler(async (event) => {
|
|
|
27
39
|
statusMessage: error.statusMessage || "Failed to fetch tweet"
|
|
28
40
|
});
|
|
29
41
|
});
|
|
42
|
+
const tweetData = structuredClone(tweetRaw);
|
|
43
|
+
const handlerPath = event.path?.split("?")[0] || "";
|
|
44
|
+
const prefix = handlerPath.replace(EMBED_X_SUFFIX_RE, "") || "/_scripts";
|
|
45
|
+
const imagePath = `${prefix}/embed/x-image`;
|
|
46
|
+
const secret = useRuntimeConfig(event)["nuxt-scripts"]?.proxySecret;
|
|
47
|
+
rewriteTweetImages(tweetData, imagePath, secret);
|
|
30
48
|
setHeader(event, "Content-Type", "application/json");
|
|
31
49
|
setHeader(event, "Cache-Control", "public, max-age=600, s-maxage=600");
|
|
32
50
|
return tweetData;
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -44,12 +44,35 @@ import type { ProxyPrivacyInput } from './server/utils/privacy.js';
|
|
|
44
44
|
export type { Cluster, ClusterStats, MarkerClustererContext, MarkerClustererInstance, MarkerClustererOptions } from './components/GoogleMaps/types.js';
|
|
45
45
|
export { MARKER_CLUSTERER_INJECTION_KEY } from './components/GoogleMaps/types.js';
|
|
46
46
|
export type WarmupStrategy = false | 'preload' | 'preconnect' | 'dns-prefetch';
|
|
47
|
-
|
|
47
|
+
/**
|
|
48
|
+
* GCMv2 consent category value.
|
|
49
|
+
* @see https://developers.google.com/tag-platform/security/guides/consent
|
|
50
|
+
*/
|
|
51
|
+
export type ConsentCategoryValue = 'granted' | 'denied';
|
|
52
|
+
/**
|
|
53
|
+
* Canonical GCMv2 consent state shape used by vendors that natively consume
|
|
54
|
+
* Consent Mode v2 (Google Analytics, Google Tag Manager, Bing UET).
|
|
55
|
+
*/
|
|
56
|
+
export interface ConsentState {
|
|
57
|
+
ad_storage?: ConsentCategoryValue;
|
|
58
|
+
ad_user_data?: ConsentCategoryValue;
|
|
59
|
+
ad_personalization?: ConsentCategoryValue;
|
|
60
|
+
analytics_storage?: ConsentCategoryValue;
|
|
61
|
+
functionality_storage?: ConsentCategoryValue;
|
|
62
|
+
personalization_storage?: ConsentCategoryValue;
|
|
63
|
+
security_storage?: ConsentCategoryValue;
|
|
64
|
+
}
|
|
65
|
+
export type UseScriptContext<T extends Record<symbol | string, any>, C = unknown> = VueScriptInstance<T> & {
|
|
48
66
|
/**
|
|
49
67
|
* Remove and reload the script. Useful for scripts that need to re-execute
|
|
50
68
|
* after SPA navigation (e.g., DOM-scanning scripts like iubenda).
|
|
51
69
|
*/
|
|
52
70
|
reload: () => Promise<T>;
|
|
71
|
+
/**
|
|
72
|
+
* Vendor-native consent controls attached by registry scripts.
|
|
73
|
+
* Shape depends on the vendor (GCMv2 update, binary grant/revoke, three-state, etc.).
|
|
74
|
+
*/
|
|
75
|
+
consent?: C;
|
|
53
76
|
};
|
|
54
77
|
export type NuxtUseScriptOptions<T extends Record<symbol | string, any> = {}> = Omit<UseScriptOptions<T>, 'trigger'> & {
|
|
55
78
|
/**
|