@nuxt/scripts 1.0.0-beta.2 → 1.0.0-beta.20
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/README.md +3 -3
- package/dist/client/200.html +1 -1
- package/dist/client/404.html +1 -1
- package/dist/client/_nuxt/{B66N9HCo.js → 9LJPrOyI.js} +1 -1
- package/dist/client/_nuxt/DFEfk2pB.js +162 -0
- package/dist/client/_nuxt/{DvH517bE.js → DMlY-BNa.js} +1 -1
- package/dist/client/_nuxt/{DfLgoB--.js → __ZZTkMj.js} +1 -1
- package/dist/client/_nuxt/builds/latest.json +1 -1
- package/dist/client/_nuxt/builds/meta/8212d4fa-7985-421b-815a-03a886e667d4.json +1 -0
- package/dist/client/_nuxt/entry.CACgbLJl.css +1 -0
- package/dist/client/_nuxt/error-404.CHeaW3dp.css +1 -0
- package/dist/client/_nuxt/error-500.DvOvWme_.css +1 -0
- package/dist/client/index.html +1 -1
- package/dist/module.d.mts +27 -18
- package/dist/module.d.ts +178 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +747 -527
- package/dist/registry.d.ts +6 -0
- package/dist/registry.mjs +74 -21
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.d.vue.ts +2 -2
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.vue +7 -7
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.vue.d.ts +2 -2
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue +6 -6
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsCircle.vue +7 -7
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsHeatmapLayer.vue +6 -6
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue +12 -12
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarker.vue +6 -6
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.d.vue.ts +1 -1
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue +6 -6
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue.d.ts +1 -1
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPinElement.vue +5 -5
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPolygon.vue +7 -7
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPolyline.vue +7 -7
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsRectangle.vue +7 -7
- package/dist/runtime/components/ScriptCrisp.vue +1 -1
- package/dist/runtime/components/ScriptGoogleAdsense.vue +1 -1
- package/dist/runtime/components/ScriptGravatar.d.vue.ts +22 -0
- package/dist/runtime/components/ScriptGravatar.vue +46 -0
- package/dist/runtime/components/ScriptGravatar.vue.d.ts +22 -0
- package/dist/runtime/components/ScriptInstagramEmbed.vue +1 -1
- package/dist/runtime/components/ScriptIntercom.vue +4 -3
- package/dist/runtime/components/ScriptPayPalButtons.d.vue.ts +43 -32
- package/dist/runtime/components/ScriptPayPalButtons.vue +48 -79
- package/dist/runtime/components/ScriptPayPalButtons.vue.d.ts +43 -32
- package/dist/runtime/components/ScriptPayPalMessages.d.vue.ts +37 -23
- package/dist/runtime/components/ScriptPayPalMessages.vue +46 -50
- package/dist/runtime/components/ScriptPayPalMessages.vue.d.ts +37 -23
- package/dist/runtime/components/ScriptStripePricingTable.vue +2 -2
- package/dist/runtime/components/ScriptVimeoPlayer.d.vue.ts +9 -0
- package/dist/runtime/components/ScriptVimeoPlayer.vue +13 -10
- package/dist/runtime/components/ScriptVimeoPlayer.vue.d.ts +9 -0
- package/dist/runtime/components/ScriptXEmbed.d.vue.ts +1 -1
- package/dist/runtime/components/ScriptXEmbed.vue +1 -1
- package/dist/runtime/components/ScriptXEmbed.vue.d.ts +1 -1
- package/dist/runtime/components/ScriptYouTubePlayer.d.vue.ts +2 -2
- package/dist/runtime/components/ScriptYouTubePlayer.vue +11 -5
- package/dist/runtime/components/ScriptYouTubePlayer.vue.d.ts +2 -2
- package/dist/runtime/composables/useScript.js +11 -6
- package/dist/runtime/composables/useScriptEventPage.js +2 -2
- package/dist/runtime/composables/useScriptTriggerConsent.js +1 -1
- package/dist/runtime/composables/useScriptTriggerElement.js +1 -1
- package/dist/runtime/composables/useScriptTriggerIdleTimeout.js +1 -1
- package/dist/runtime/registry/clarity.d.ts +10 -15
- package/dist/runtime/registry/clarity.js +22 -31
- package/dist/runtime/registry/cloudflare-web-analytics.d.ts +2 -13
- package/dist/runtime/registry/cloudflare-web-analytics.js +2 -14
- package/dist/runtime/registry/crisp.d.ts +9 -39
- package/dist/runtime/registry/crisp.js +2 -33
- package/dist/runtime/registry/databuddy-analytics.d.ts +2 -35
- package/dist/runtime/registry/databuddy-analytics.js +20 -45
- package/dist/runtime/registry/fathom-analytics.d.ts +6 -25
- package/dist/runtime/registry/fathom-analytics.js +2 -24
- package/dist/runtime/registry/google-adsense.d.ts +2 -10
- package/dist/runtime/registry/google-adsense.js +2 -11
- package/dist/runtime/registry/google-analytics.d.ts +3 -5
- package/dist/runtime/registry/google-analytics.js +3 -8
- package/dist/runtime/registry/google-maps.d.ts +3 -9
- package/dist/runtime/registry/google-maps.js +2 -8
- package/dist/runtime/registry/google-recaptcha.d.ts +2 -6
- package/dist/runtime/registry/google-recaptcha.js +4 -12
- package/dist/runtime/registry/google-sign-in.d.ts +2 -13
- package/dist/runtime/registry/google-sign-in.js +2 -22
- package/dist/runtime/registry/google-tag-manager.d.ts +3 -28
- package/dist/runtime/registry/google-tag-manager.js +4 -27
- package/dist/runtime/registry/gravatar.d.ts +25 -0
- package/dist/runtime/registry/gravatar.js +32 -0
- package/dist/runtime/registry/hotjar.d.ts +3 -5
- package/dist/runtime/registry/hotjar.js +2 -5
- package/dist/runtime/registry/instagram-embed.d.ts +2 -17
- package/dist/runtime/registry/instagram-embed.js +4 -19
- package/dist/runtime/registry/intercom.d.ts +3 -11
- package/dist/runtime/registry/intercom.js +2 -12
- package/dist/runtime/registry/matomo-analytics.d.ts +2 -11
- package/dist/runtime/registry/matomo-analytics.js +3 -12
- package/dist/runtime/registry/meta-pixel.d.ts +3 -5
- package/dist/runtime/registry/meta-pixel.js +2 -4
- package/dist/runtime/registry/npm.d.ts +2 -6
- package/dist/runtime/registry/npm.js +2 -9
- package/dist/runtime/registry/paypal.d.ts +4 -25
- package/dist/runtime/registry/paypal.js +3 -66
- package/dist/runtime/registry/plausible-analytics.js +18 -13
- package/dist/runtime/registry/posthog.d.ts +10 -11
- package/dist/runtime/registry/posthog.js +7 -20
- package/dist/runtime/registry/reddit-pixel.d.ts +4 -5
- package/dist/runtime/registry/reddit-pixel.js +2 -4
- package/dist/runtime/registry/rybbit-analytics.d.ts +2 -14
- package/dist/runtime/registry/rybbit-analytics.js +7 -19
- package/dist/runtime/registry/schemas.d.ts +946 -0
- package/dist/runtime/registry/schemas.js +901 -0
- package/dist/runtime/registry/segment.d.ts +2 -5
- package/dist/runtime/registry/segment.js +2 -5
- package/dist/runtime/registry/snapchat-pixel.d.ts +3 -32
- package/dist/runtime/registry/snapchat-pixel.js +2 -20
- package/dist/runtime/registry/stripe.d.ts +3 -4
- package/dist/runtime/registry/stripe.js +2 -4
- package/dist/runtime/registry/tiktok-pixel.d.ts +3 -6
- package/dist/runtime/registry/tiktok-pixel.js +2 -6
- package/dist/runtime/registry/umami-analytics.d.ts +2 -31
- package/dist/runtime/registry/umami-analytics.js +2 -36
- package/dist/runtime/registry/vercel-analytics.d.ts +29 -0
- package/dist/runtime/registry/vercel-analytics.js +84 -0
- package/dist/runtime/registry/vimeo-player.d.ts +2 -2
- package/dist/runtime/registry/vimeo-player.js +1 -1
- package/dist/runtime/registry/x-embed.d.ts +2 -16
- package/dist/runtime/registry/x-embed.js +2 -17
- package/dist/runtime/registry/x-pixel.d.ts +3 -6
- package/dist/runtime/registry/x-pixel.js +2 -5
- package/dist/runtime/registry/youtube-player.d.ts +7 -7
- package/dist/runtime/registry/youtube-player.js +1 -1
- package/dist/runtime/server/google-static-maps-proxy.js +1 -1
- package/dist/runtime/server/{sw-handler.d.ts → gravatar-proxy.d.ts} +1 -1
- package/dist/runtime/server/gravatar-proxy.js +62 -0
- package/dist/runtime/server/instagram-embed-asset.js +2 -1
- package/dist/runtime/server/instagram-embed-image.js +2 -1
- package/dist/runtime/server/instagram-embed.js +22 -13
- package/dist/runtime/server/proxy-handler.js +161 -117
- package/dist/runtime/server/utils/privacy.d.ts +45 -1
- package/dist/runtime/server/utils/privacy.js +103 -40
- package/dist/runtime/server/x-embed.js +3 -2
- package/dist/runtime/types.d.ts +30 -24
- package/dist/runtime/utils/pure.d.ts +0 -4
- package/dist/runtime/utils/pure.js +0 -67
- package/dist/runtime/utils.d.ts +3 -3
- package/dist/runtime/utils.js +12 -7
- package/dist/shared/scripts.Crpn87WB.mjs +318 -0
- package/dist/stats.d.mts +39 -0
- package/dist/stats.d.ts +39 -0
- package/dist/stats.mjs +772 -0
- package/dist/types-source.d.mts +19 -0
- package/dist/types-source.d.ts +19 -0
- package/dist/types-source.mjs +975 -0
- package/package.json +42 -31
- package/dist/client/_nuxt/B8XOar-X.js +0 -162
- package/dist/client/_nuxt/builds/meta/133a46c5-a5c1-4a63-87d1-037947a5bcdb.json +0 -1
- package/dist/client/_nuxt/entry.D45OuV0w.css +0 -1
- package/dist/client/_nuxt/error-404.B57D-jUQ.css +0 -1
- package/dist/client/_nuxt/error-500.DTHUW7BI.css +0 -1
- package/dist/runtime/components/ScriptPayPalMarks.d.vue.ts +0 -52
- package/dist/runtime/components/ScriptPayPalMarks.vue +0 -69
- package/dist/runtime/components/ScriptPayPalMarks.vue.d.ts +0 -52
- package/dist/runtime/plugins/sw-register.client.d.ts +0 -2
- package/dist/runtime/plugins/sw-register.client.js +0 -12
- package/dist/runtime/server/sw-handler.js +0 -25
- package/dist/runtime/sw/proxy-sw.template.d.ts +0 -1
- package/dist/runtime/sw/proxy-sw.template.js +0 -54
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { createError, defineEventHandler, getQuery, setHeader } from "h3";
|
|
2
2
|
import { $fetch } from "ofetch";
|
|
3
|
+
const LINK_RE = /<link[^>]+rel=["']stylesheet["'][^>]+href=["']([^"']+)["'][^>]*>/gi;
|
|
4
|
+
const LINK_RE_2 = /<link[^>]+href=["']([^"']+)["'][^>]+rel=["']stylesheet["'][^>]*>/gi;
|
|
5
|
+
const RSRC_RE = /url\(\/rsrc\.php([^)]+)\)/g;
|
|
6
|
+
const SCRIPT_RE = /<script[\s\S]*?<\/script>/gi;
|
|
7
|
+
const STYLESHEET_RE = /<link[^>]+rel=["']stylesheet["'][^>]*>/gi;
|
|
8
|
+
const CSS_RE = /<link[^>]+href=["'][^"']+\.css[^"']*["'][^>]*>/gi;
|
|
9
|
+
const NOSCRIPT_RE = /<noscript>[\s\S]*?<\/noscript>/gi;
|
|
10
|
+
const SCONTENT_RE = /https:\/\/scontent[^"'\s),]+\.cdninstagram\.com[^"'\s),]+/g;
|
|
11
|
+
const STATIC_CDN_RE = /https:\/\/static\.cdninstagram\.com[^"'\s),]+/g;
|
|
12
|
+
const LOOKASIDE_RE = /https:\/\/lookaside\.instagram\.com[^"'\s),]+/g;
|
|
13
|
+
const AMP_RE = /&/g;
|
|
3
14
|
export default defineEventHandler(async (event) => {
|
|
4
15
|
const query = getQuery(event);
|
|
5
16
|
const postUrl = query.url;
|
|
@@ -27,7 +38,7 @@ export default defineEventHandler(async (event) => {
|
|
|
27
38
|
}
|
|
28
39
|
const pathname = parsedUrl.pathname.endsWith("/") ? parsedUrl.pathname : `${parsedUrl.pathname}/`;
|
|
29
40
|
const cleanUrl = parsedUrl.origin + pathname;
|
|
30
|
-
const embedUrl = cleanUrl
|
|
41
|
+
const embedUrl = `${cleanUrl}embed/${captions ? "captioned/" : ""}`;
|
|
31
42
|
const html = await $fetch(embedUrl, {
|
|
32
43
|
headers: {
|
|
33
44
|
"Accept": "text/html",
|
|
@@ -41,14 +52,12 @@ export default defineEventHandler(async (event) => {
|
|
|
41
52
|
});
|
|
42
53
|
});
|
|
43
54
|
const cssUrls = [];
|
|
44
|
-
const linkRegex = /<link[^>]+rel=["']stylesheet["'][^>]+href=["']([^"']+)["'][^>]*>/gi;
|
|
45
55
|
let match;
|
|
46
|
-
while ((match =
|
|
56
|
+
while ((match = LINK_RE.exec(html)) !== null) {
|
|
47
57
|
if (match[1])
|
|
48
58
|
cssUrls.push(match[1]);
|
|
49
59
|
}
|
|
50
|
-
|
|
51
|
-
while ((match = linkRegex2.exec(html)) !== null) {
|
|
60
|
+
while ((match = LINK_RE_2.exec(html)) !== null) {
|
|
52
61
|
if (match[1])
|
|
53
62
|
cssUrls.push(match[1]);
|
|
54
63
|
}
|
|
@@ -61,7 +70,7 @@ export default defineEventHandler(async (event) => {
|
|
|
61
70
|
);
|
|
62
71
|
let combinedCss = cssContents.join("\n");
|
|
63
72
|
combinedCss = combinedCss.replace(
|
|
64
|
-
|
|
73
|
+
RSRC_RE,
|
|
65
74
|
(_m, path) => `url(/api/_scripts/instagram-embed-asset?url=${encodeURIComponent(`https://static.cdninstagram.com/rsrc.php${path}`)})`
|
|
66
75
|
);
|
|
67
76
|
const baseStyles = `
|
|
@@ -70,15 +79,15 @@ export default defineEventHandler(async (event) => {
|
|
|
70
79
|
.Embed { opacity: 1 !important; visibility: visible !important; }
|
|
71
80
|
.EmbeddedMedia, .EmbeddedMediaImage { display: block !important; visibility: visible !important; }
|
|
72
81
|
`;
|
|
73
|
-
let rewrittenHtml = html.replace(
|
|
74
|
-
|
|
75
|
-
(m) => `/api/_scripts/instagram-embed-image?url=${encodeURIComponent(m.replace(
|
|
82
|
+
let rewrittenHtml = html.replace(SCRIPT_RE, "").replace(STYLESHEET_RE, "").replace(CSS_RE, "").replace(NOSCRIPT_RE, "").replace(
|
|
83
|
+
SCONTENT_RE,
|
|
84
|
+
(m) => `/api/_scripts/instagram-embed-image?url=${encodeURIComponent(m.replace(AMP_RE, "&"))}`
|
|
76
85
|
).replace(
|
|
77
|
-
|
|
78
|
-
(m) => `/api/_scripts/instagram-embed-asset?url=${encodeURIComponent(m.replace(
|
|
86
|
+
STATIC_CDN_RE,
|
|
87
|
+
(m) => `/api/_scripts/instagram-embed-asset?url=${encodeURIComponent(m.replace(AMP_RE, "&"))}`
|
|
79
88
|
).replace(
|
|
80
|
-
|
|
81
|
-
(m) => `/api/_scripts/instagram-embed-image?url=${encodeURIComponent(m.replace(
|
|
89
|
+
LOOKASIDE_RE,
|
|
90
|
+
(m) => `/api/_scripts/instagram-embed-image?url=${encodeURIComponent(m.replace(AMP_RE, "&"))}`
|
|
82
91
|
);
|
|
83
92
|
rewrittenHtml = rewrittenHtml.replace(
|
|
84
93
|
"</head>",
|
|
@@ -1,30 +1,40 @@
|
|
|
1
|
-
import { defineEventHandler, getHeaders, getRequestIP, readBody, getQuery, setResponseHeader, createError } from "h3";
|
|
2
1
|
import { useRuntimeConfig } from "#imports";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { rewriteScriptUrls } from "../utils/pure.js";
|
|
2
|
+
import { createError, defineEventHandler, getHeaders, getQuery, getRequestIP, getRequestWebStream, readBody, setResponseHeader } from "h3";
|
|
3
|
+
import { useNitroApp } from "nitropack/runtime";
|
|
6
4
|
import {
|
|
7
|
-
FINGERPRINT_HEADERS,
|
|
8
|
-
IP_HEADERS,
|
|
9
|
-
SENSITIVE_HEADERS,
|
|
10
5
|
anonymizeIP,
|
|
6
|
+
mergePrivacy,
|
|
11
7
|
normalizeLanguage,
|
|
12
8
|
normalizeUserAgent,
|
|
9
|
+
resolvePrivacy,
|
|
10
|
+
SENSITIVE_HEADERS,
|
|
13
11
|
stripPayloadFingerprinting
|
|
14
12
|
} from "./utils/privacy.js";
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
const COMPRESSION_RE = /gzip|deflate|br|compress|base64/i;
|
|
14
|
+
const ROUTE_WILDCARD_RE = /\/\*\*$/;
|
|
15
|
+
const CLIENT_HINT_VERSION_RE = /;v="(\d+)\.[^"]*"/g;
|
|
16
|
+
const SKIP_RESPONSE_HEADERS = /* @__PURE__ */ new Set(["set-cookie", "transfer-encoding", "content-encoding", "content-length"]);
|
|
17
|
+
let sortedRoutesCache;
|
|
18
|
+
function getSortedRoutes(routes) {
|
|
19
|
+
const key = JSON.stringify(routes);
|
|
20
|
+
if (sortedRoutesCache?.key === key)
|
|
21
|
+
return sortedRoutesCache.sorted;
|
|
22
|
+
const sorted = Object.entries(routes).sort((a, b) => b[0].length - a[0].length);
|
|
23
|
+
sortedRoutesCache = { key, sorted };
|
|
24
|
+
return sorted;
|
|
25
|
+
}
|
|
26
|
+
function stripQueryFingerprinting(query, privacy) {
|
|
27
|
+
const stripped = stripPayloadFingerprinting(query, privacy);
|
|
17
28
|
const params = new URLSearchParams();
|
|
18
29
|
for (const [key, value] of Object.entries(stripped)) {
|
|
19
30
|
if (value !== void 0 && value !== null) {
|
|
20
|
-
params.set(key, String(value));
|
|
31
|
+
params.set(key, typeof value === "object" ? JSON.stringify(value) : String(value));
|
|
21
32
|
}
|
|
22
33
|
}
|
|
23
|
-
return params.toString();
|
|
34
|
+
return { queryString: params.toString(), stripped };
|
|
24
35
|
}
|
|
25
36
|
export default defineEventHandler(async (event) => {
|
|
26
37
|
const config = useRuntimeConfig();
|
|
27
|
-
const nitro = useNitroApp();
|
|
28
38
|
const proxyConfig = config["nuxt-scripts-proxy"];
|
|
29
39
|
if (!proxyConfig) {
|
|
30
40
|
throw createError({
|
|
@@ -32,7 +42,7 @@ export default defineEventHandler(async (event) => {
|
|
|
32
42
|
statusMessage: "First-party proxy not configured"
|
|
33
43
|
});
|
|
34
44
|
}
|
|
35
|
-
const { routes, privacy,
|
|
45
|
+
const { routes, privacy: globalPrivacy, routePrivacy, debug = import.meta.dev } = proxyConfig;
|
|
36
46
|
const path = event.path;
|
|
37
47
|
const log = debug ? (message, ...args) => {
|
|
38
48
|
console.debug(message, ...args);
|
|
@@ -40,17 +50,18 @@ export default defineEventHandler(async (event) => {
|
|
|
40
50
|
};
|
|
41
51
|
let targetBase;
|
|
42
52
|
let matchedPrefix;
|
|
43
|
-
|
|
44
|
-
for (const [routePattern, target] of
|
|
45
|
-
const prefix = routePattern.replace(
|
|
53
|
+
let matchedRoutePattern;
|
|
54
|
+
for (const [routePattern, target] of getSortedRoutes(routes)) {
|
|
55
|
+
const prefix = routePattern.replace(ROUTE_WILDCARD_RE, "");
|
|
46
56
|
if (path.startsWith(prefix)) {
|
|
47
|
-
targetBase = target.replace(
|
|
57
|
+
targetBase = target.replace(ROUTE_WILDCARD_RE, "");
|
|
48
58
|
matchedPrefix = prefix;
|
|
59
|
+
matchedRoutePattern = routePattern;
|
|
49
60
|
log("[proxy] Matched:", prefix, "->", targetBase);
|
|
50
61
|
break;
|
|
51
62
|
}
|
|
52
63
|
}
|
|
53
|
-
if (!targetBase || !matchedPrefix) {
|
|
64
|
+
if (!targetBase || !matchedPrefix || !matchedRoutePattern) {
|
|
54
65
|
log("[proxy] No match for path:", path);
|
|
55
66
|
throw createError({
|
|
56
67
|
statusCode: 404,
|
|
@@ -58,149 +69,197 @@ export default defineEventHandler(async (event) => {
|
|
|
58
69
|
message: `No proxy target found for path: ${path}`
|
|
59
70
|
});
|
|
60
71
|
}
|
|
72
|
+
const perScriptInput = routePrivacy[matchedRoutePattern];
|
|
73
|
+
if (debug && perScriptInput === void 0) {
|
|
74
|
+
log("[proxy] WARNING: No privacy config for route", matchedRoutePattern, "\u2014 defaulting to full anonymization");
|
|
75
|
+
}
|
|
76
|
+
const perScriptResolved = resolvePrivacy(perScriptInput ?? true);
|
|
77
|
+
const privacy = globalPrivacy !== void 0 ? mergePrivacy(perScriptResolved, globalPrivacy) : perScriptResolved;
|
|
78
|
+
const anyPrivacy = privacy.ip || privacy.userAgent || privacy.language || privacy.screen || privacy.timezone || privacy.hardware;
|
|
79
|
+
const originalHeaders = getHeaders(event);
|
|
80
|
+
const originalQuery = getQuery(event);
|
|
81
|
+
const contentType = originalHeaders["content-type"] || "";
|
|
82
|
+
const compressionParam = originalQuery.compression || "";
|
|
83
|
+
const isBinaryBody = Boolean(
|
|
84
|
+
originalHeaders["content-encoding"] || contentType.includes("octet-stream") || compressionParam && COMPRESSION_RE.test(compressionParam)
|
|
85
|
+
);
|
|
61
86
|
let targetPath = path.slice(matchedPrefix.length);
|
|
62
87
|
if (targetPath && !targetPath.startsWith("/")) {
|
|
63
|
-
targetPath =
|
|
88
|
+
targetPath = `/${targetPath}`;
|
|
64
89
|
}
|
|
65
90
|
let targetUrl = targetBase + targetPath;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (Object.keys(
|
|
69
|
-
const
|
|
91
|
+
let strippedQueryRecord;
|
|
92
|
+
if (anyPrivacy) {
|
|
93
|
+
if (Object.keys(originalQuery).length > 0) {
|
|
94
|
+
const { queryString, stripped } = stripQueryFingerprinting(originalQuery, privacy);
|
|
95
|
+
strippedQueryRecord = stripped;
|
|
70
96
|
const basePath = targetUrl.split("?")[0] || targetUrl;
|
|
71
|
-
targetUrl =
|
|
97
|
+
targetUrl = queryString ? `${basePath}?${queryString}` : basePath;
|
|
72
98
|
}
|
|
73
99
|
}
|
|
74
|
-
const originalHeaders = getHeaders(event);
|
|
75
100
|
const headers = {};
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const lowerKey = key.toLowerCase();
|
|
87
|
-
if (IP_HEADERS.includes(lowerKey))
|
|
101
|
+
for (const [key, value] of Object.entries(originalHeaders)) {
|
|
102
|
+
if (!value)
|
|
103
|
+
continue;
|
|
104
|
+
const lowerKey = key.toLowerCase();
|
|
105
|
+
if (lowerKey === "host")
|
|
106
|
+
continue;
|
|
107
|
+
if (SENSITIVE_HEADERS.includes(lowerKey))
|
|
108
|
+
continue;
|
|
109
|
+
if (lowerKey === "content-length") {
|
|
110
|
+
if (anyPrivacy && !isBinaryBody)
|
|
88
111
|
continue;
|
|
89
|
-
|
|
112
|
+
headers[lowerKey] = value;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (lowerKey === "x-forwarded-for" || lowerKey === "x-real-ip" || lowerKey === "forwarded" || lowerKey === "cf-connecting-ip" || lowerKey === "true-client-ip" || lowerKey === "x-client-ip" || lowerKey === "x-cluster-client-ip") {
|
|
116
|
+
if (privacy.ip)
|
|
90
117
|
continue;
|
|
91
|
-
|
|
118
|
+
headers[lowerKey] = value;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (lowerKey === "user-agent") {
|
|
122
|
+
headers[key] = privacy.userAgent ? normalizeUserAgent(value) : value;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (lowerKey === "accept-language") {
|
|
126
|
+
headers[key] = privacy.language ? normalizeLanguage(value) : value;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (lowerKey === "sec-ch-ua" || lowerKey === "sec-ch-ua-full-version-list") {
|
|
130
|
+
headers[lowerKey] = privacy.hardware ? value.replace(CLIENT_HINT_VERSION_RE, ';v="$1"') : value;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (lowerKey === "sec-ch-ua-platform-version" || lowerKey === "sec-ch-ua-arch" || lowerKey === "sec-ch-ua-model" || lowerKey === "sec-ch-ua-bitness") {
|
|
134
|
+
if (privacy.hardware)
|
|
92
135
|
continue;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
} else if (lowerKey === "accept-language") {
|
|
96
|
-
headers[key] = normalizeLanguage(value);
|
|
97
|
-
} else if (lowerKey === "sec-ch-ua" || lowerKey === "sec-ch-ua-full-version-list") {
|
|
98
|
-
headers[key] = value.replace(/;v="(\d+)\.[^"]*"/g, ';v="$1"');
|
|
99
|
-
} else if (FINGERPRINT_HEADERS.includes(lowerKey)) {
|
|
100
|
-
headers[key] = value;
|
|
101
|
-
} else {
|
|
102
|
-
headers[key] = value;
|
|
103
|
-
}
|
|
136
|
+
headers[lowerKey] = value;
|
|
137
|
+
continue;
|
|
104
138
|
}
|
|
139
|
+
headers[key] = value;
|
|
140
|
+
}
|
|
141
|
+
if (!headers["x-forwarded-for"]) {
|
|
105
142
|
const clientIP = getRequestIP(event, { xForwardedFor: true });
|
|
106
143
|
if (clientIP) {
|
|
107
|
-
|
|
144
|
+
if (privacy.ip) {
|
|
145
|
+
headers["x-forwarded-for"] = anonymizeIP(clientIP);
|
|
146
|
+
} else {
|
|
147
|
+
headers["x-forwarded-for"] = clientIP;
|
|
148
|
+
}
|
|
108
149
|
}
|
|
150
|
+
} else if (privacy.ip) {
|
|
151
|
+
headers["x-forwarded-for"] = headers["x-forwarded-for"].split(",").map((ip) => anonymizeIP(ip.trim())).join(", ");
|
|
109
152
|
}
|
|
110
153
|
let body;
|
|
111
154
|
let rawBody;
|
|
112
|
-
|
|
155
|
+
let passthroughBody = false;
|
|
113
156
|
const method = event.method?.toUpperCase();
|
|
114
|
-
const
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
157
|
+
const isWriteMethod = method === "POST" || method === "PUT" || method === "PATCH";
|
|
158
|
+
if (isWriteMethod) {
|
|
159
|
+
if (isBinaryBody || !anyPrivacy) {
|
|
160
|
+
passthroughBody = true;
|
|
161
|
+
} else {
|
|
162
|
+
rawBody = await readBody(event);
|
|
163
|
+
if (rawBody != null) {
|
|
164
|
+
if (Array.isArray(rawBody)) {
|
|
165
|
+
body = rawBody.map(
|
|
166
|
+
(item) => item && typeof item === "object" && !Array.isArray(item) ? stripPayloadFingerprinting(item, privacy) : item
|
|
167
|
+
);
|
|
168
|
+
} else if (typeof rawBody === "object") {
|
|
169
|
+
body = stripPayloadFingerprinting(rawBody, privacy);
|
|
170
|
+
} else if (typeof rawBody === "string") {
|
|
171
|
+
if (rawBody.startsWith("{") || rawBody.startsWith("[")) {
|
|
172
|
+
let parsed = null;
|
|
173
|
+
try {
|
|
174
|
+
parsed = JSON.parse(rawBody);
|
|
175
|
+
} catch {
|
|
176
|
+
}
|
|
177
|
+
if (Array.isArray(parsed)) {
|
|
178
|
+
body = parsed.map(
|
|
179
|
+
(item) => item && typeof item === "object" && !Array.isArray(item) ? stripPayloadFingerprinting(item, privacy) : item
|
|
180
|
+
);
|
|
181
|
+
} else if (parsed && typeof parsed === "object") {
|
|
182
|
+
body = stripPayloadFingerprinting(parsed, privacy);
|
|
183
|
+
} else {
|
|
184
|
+
body = rawBody;
|
|
185
|
+
}
|
|
186
|
+
} else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
187
|
+
const params = new URLSearchParams(rawBody);
|
|
188
|
+
const obj = {};
|
|
189
|
+
params.forEach((value, key) => {
|
|
190
|
+
obj[key] = value;
|
|
191
|
+
});
|
|
192
|
+
const stripped = stripPayloadFingerprinting(obj, privacy);
|
|
193
|
+
const stringified = {};
|
|
194
|
+
for (const [k, v] of Object.entries(stripped)) {
|
|
195
|
+
if (v === void 0 || v === null)
|
|
196
|
+
continue;
|
|
197
|
+
stringified[k] = typeof v === "string" ? v : JSON.stringify(v);
|
|
198
|
+
}
|
|
199
|
+
body = new URLSearchParams(stringified).toString();
|
|
129
200
|
} else {
|
|
130
201
|
body = rawBody;
|
|
131
202
|
}
|
|
132
|
-
} else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
133
|
-
const params = new URLSearchParams(rawBody);
|
|
134
|
-
const obj = {};
|
|
135
|
-
params.forEach((value, key) => {
|
|
136
|
-
obj[key] = value;
|
|
137
|
-
});
|
|
138
|
-
const stripped = stripPayloadFingerprinting(obj);
|
|
139
|
-
const stringified = {};
|
|
140
|
-
for (const [k, v] of Object.entries(stripped)) {
|
|
141
|
-
if (v === void 0 || v === null) continue;
|
|
142
|
-
stringified[k] = typeof v === "string" ? v : JSON.stringify(v);
|
|
143
|
-
}
|
|
144
|
-
body = new URLSearchParams(stringified).toString();
|
|
145
203
|
} else {
|
|
146
204
|
body = rawBody;
|
|
147
205
|
}
|
|
148
|
-
} else {
|
|
149
|
-
body = rawBody;
|
|
150
206
|
}
|
|
151
|
-
} else {
|
|
152
|
-
body = rawBody;
|
|
153
207
|
}
|
|
154
208
|
}
|
|
209
|
+
const nitro = useNitroApp();
|
|
155
210
|
await nitro.hooks.callHook("nuxt-scripts:proxy", {
|
|
156
211
|
timestamp: Date.now(),
|
|
157
212
|
path: event.path,
|
|
158
213
|
targetUrl,
|
|
159
214
|
method: method || "GET",
|
|
160
215
|
privacy,
|
|
216
|
+
passthroughBody,
|
|
161
217
|
original: {
|
|
162
218
|
headers: { ...originalHeaders },
|
|
163
219
|
query: originalQuery,
|
|
164
|
-
body: rawBody ?? null
|
|
220
|
+
body: passthroughBody ? "<passthrough>" : rawBody ?? null
|
|
165
221
|
},
|
|
166
222
|
stripped: {
|
|
167
223
|
headers,
|
|
168
|
-
query:
|
|
169
|
-
body: body ?? null
|
|
224
|
+
query: strippedQueryRecord ?? originalQuery,
|
|
225
|
+
body: passthroughBody ? "<passthrough>" : body ?? null
|
|
170
226
|
}
|
|
171
227
|
});
|
|
172
228
|
log("[proxy] Fetching:", targetUrl);
|
|
173
229
|
const controller = new AbortController();
|
|
174
230
|
const timeoutId = setTimeout(() => controller.abort(), 15e3);
|
|
231
|
+
let fetchBody;
|
|
232
|
+
if (passthroughBody) {
|
|
233
|
+
fetchBody = getRequestWebStream(event);
|
|
234
|
+
} else if (body !== void 0) {
|
|
235
|
+
fetchBody = typeof body === "string" ? body : JSON.stringify(body);
|
|
236
|
+
}
|
|
175
237
|
let response;
|
|
176
238
|
try {
|
|
177
239
|
response = await fetch(targetUrl, {
|
|
178
240
|
method: method || "GET",
|
|
179
241
|
headers,
|
|
180
|
-
body:
|
|
242
|
+
body: fetchBody,
|
|
181
243
|
credentials: "omit",
|
|
182
244
|
// Don't send cookies to third parties
|
|
183
|
-
signal: controller.signal
|
|
245
|
+
signal: controller.signal,
|
|
246
|
+
// @ts-expect-error Node fetch supports duplex for streaming request bodies
|
|
247
|
+
duplex: passthroughBody ? "half" : void 0
|
|
184
248
|
});
|
|
185
249
|
} catch (err) {
|
|
186
250
|
clearTimeout(timeoutId);
|
|
187
|
-
log("[proxy]
|
|
188
|
-
if (path.includes("/collect") || path.includes("/tr") || path.includes("/events")) {
|
|
189
|
-
event.node.res.statusCode = 204;
|
|
190
|
-
return "";
|
|
191
|
-
}
|
|
192
|
-
const isTimeout = err instanceof Error && (err.message.includes("aborted") || err.message.includes("timeout"));
|
|
251
|
+
log("[proxy] Upstream error:", err);
|
|
193
252
|
throw createError({
|
|
194
|
-
statusCode:
|
|
195
|
-
statusMessage:
|
|
196
|
-
message:
|
|
253
|
+
statusCode: 502,
|
|
254
|
+
statusMessage: "Bad Gateway",
|
|
255
|
+
message: `Proxy upstream request failed: ${targetUrl}`
|
|
197
256
|
});
|
|
257
|
+
} finally {
|
|
258
|
+
clearTimeout(timeoutId);
|
|
198
259
|
}
|
|
199
|
-
clearTimeout(timeoutId);
|
|
200
260
|
log("[proxy] Response:", response.status, response.statusText);
|
|
201
|
-
const skipHeaders = ["set-cookie", "transfer-encoding", "content-encoding", "content-length"];
|
|
202
261
|
response.headers.forEach((value, key) => {
|
|
203
|
-
if (!
|
|
262
|
+
if (!SKIP_RESPONSE_HEADERS.has(key.toLowerCase())) {
|
|
204
263
|
setResponseHeader(event, key, value);
|
|
205
264
|
}
|
|
206
265
|
});
|
|
@@ -209,22 +268,7 @@ export default defineEventHandler(async (event) => {
|
|
|
209
268
|
const responseContentType = response.headers.get("content-type") || "";
|
|
210
269
|
const isTextContent = responseContentType.includes("text") || responseContentType.includes("javascript") || responseContentType.includes("json");
|
|
211
270
|
if (isTextContent) {
|
|
212
|
-
|
|
213
|
-
if (responseContentType.includes("javascript") && proxyConfig?.rewrites?.length) {
|
|
214
|
-
const cacheKey = `nuxt-scripts:proxy:${hash(targetUrl + JSON.stringify(proxyConfig.rewrites))}`;
|
|
215
|
-
const storage = useStorage("cache");
|
|
216
|
-
const cached = await storage.getItem(cacheKey);
|
|
217
|
-
if (cached && typeof cached === "string") {
|
|
218
|
-
log("[proxy] Serving rewritten script from cache");
|
|
219
|
-
content = cached;
|
|
220
|
-
} else {
|
|
221
|
-
content = rewriteScriptUrls(content, proxyConfig.rewrites);
|
|
222
|
-
await storage.setItem(cacheKey, content, { ttl: cacheTtl });
|
|
223
|
-
log("[proxy] Rewrote URLs in JavaScript response and cached");
|
|
224
|
-
}
|
|
225
|
-
setResponseHeader(event, "cache-control", `public, max-age=${cacheTtl}, stale-while-revalidate=${cacheTtl * 2}`);
|
|
226
|
-
}
|
|
227
|
-
return content;
|
|
271
|
+
return await response.text();
|
|
228
272
|
}
|
|
229
273
|
return Buffer.from(await response.arrayBuffer());
|
|
230
274
|
});
|
|
@@ -1,3 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Granular privacy controls for the first-party proxy.
|
|
3
|
+
* Each flag controls both headers AND body/query params for its domain.
|
|
4
|
+
*/
|
|
5
|
+
export interface ProxyPrivacy {
|
|
6
|
+
/** Anonymize IP (headers + body). When false, real IP is forwarded via x-forwarded-for. */
|
|
7
|
+
ip?: boolean;
|
|
8
|
+
/** Normalize User-Agent (headers + body) */
|
|
9
|
+
userAgent?: boolean;
|
|
10
|
+
/** Normalize Accept-Language (headers + body) */
|
|
11
|
+
language?: boolean;
|
|
12
|
+
/** Generalize screen resolution, viewport, hardware concurrency, device memory */
|
|
13
|
+
screen?: boolean;
|
|
14
|
+
/** Generalize timezone offset and IANA timezone names */
|
|
15
|
+
timezone?: boolean;
|
|
16
|
+
/** Anonymize hardware fingerprints: canvas/webgl/audio, plugins/fonts, browser versions, device info */
|
|
17
|
+
hardware?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Privacy input: `true` = full anonymize, `false` = passthrough (still strips sensitive headers),
|
|
21
|
+
* or a `ProxyPrivacy` object for granular control (unset flags default to `false` — opt-in).
|
|
22
|
+
*/
|
|
23
|
+
export type ProxyPrivacyInput = boolean | ProxyPrivacy | null;
|
|
24
|
+
/** Resolved privacy with all flags explicitly set. */
|
|
25
|
+
export type ResolvedProxyPrivacy = Required<ProxyPrivacy>;
|
|
26
|
+
/**
|
|
27
|
+
* Normalize a privacy input to a fully-resolved object.
|
|
28
|
+
* Privacy is opt-in: unset object flags default to `false`.
|
|
29
|
+
* Each script in the registry explicitly sets all flags for its needs.
|
|
30
|
+
* - `true` → all flags true (full anonymize)
|
|
31
|
+
* - `false` / `undefined` → all flags false (passthrough)
|
|
32
|
+
* - `{ ip: true, hardware: true }` → only those active, rest off
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolvePrivacy(input?: ProxyPrivacyInput): ResolvedProxyPrivacy;
|
|
35
|
+
/**
|
|
36
|
+
* Merge privacy settings: `override` fields take precedence over `base` field-by-field.
|
|
37
|
+
* When `override` is undefined, returns `base` unchanged.
|
|
38
|
+
* When `override` is a boolean, it fully replaces `base`.
|
|
39
|
+
* When `override` is an object, only explicitly-set fields override.
|
|
40
|
+
*/
|
|
41
|
+
export declare function mergePrivacy(base: ResolvedProxyPrivacy, override?: ProxyPrivacyInput): ResolvedProxyPrivacy;
|
|
1
42
|
/**
|
|
2
43
|
* Headers that reveal user IP address - stripped in proxy mode,
|
|
3
44
|
* anonymized in anonymize mode.
|
|
@@ -93,5 +134,8 @@ export declare function anonymizeDeviceInfo(value: string): string;
|
|
|
93
134
|
* Recursively anonymize fingerprinting data in payload.
|
|
94
135
|
* Fields are generalized or normalized rather than stripped, so endpoints
|
|
95
136
|
* still receive valid data with reduced fingerprinting precision.
|
|
137
|
+
*
|
|
138
|
+
* When `privacy` is provided, only categories with their flag set to `true` are processed.
|
|
139
|
+
* Default (no arg) = all categories active, so existing callers work unchanged.
|
|
96
140
|
*/
|
|
97
|
-
export declare function stripPayloadFingerprinting(payload: Record<string, unknown
|
|
141
|
+
export declare function stripPayloadFingerprinting(payload: Record<string, unknown>, privacy?: ResolvedProxyPrivacy): Record<string, unknown>;
|