@nuxt/scripts 1.0.0-beta.1 → 1.0.0-beta.13
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 +6 -0
- package/dist/client/200.html +1 -1
- package/dist/client/404.html +1 -1
- package/dist/client/_nuxt/48AF9EJD.js +1 -0
- package/dist/client/_nuxt/Bk6ed9rg.js +1 -0
- package/dist/client/_nuxt/C4Cj8gBr.js +162 -0
- package/dist/client/_nuxt/{DTDyDxvR.js → DP0kj6Xn.js} +1 -1
- package/dist/client/_nuxt/builds/latest.json +1 -1
- package/dist/client/_nuxt/builds/meta/0e95b1cc-8751-4000-8cb4-a6ef270b636f.json +1 -0
- package/dist/client/_nuxt/entry.D45OuV0w.css +1 -0
- package/dist/client/_nuxt/error-404.B57D-jUQ.css +1 -0
- package/dist/client/_nuxt/error-500.DTHUW7BI.css +1 -0
- package/dist/client/index.html +1 -1
- package/dist/module.d.mts +87 -2
- package/dist/module.d.ts +176 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +710 -273
- package/dist/registry.d.ts +6 -0
- package/dist/registry.mjs +42 -19
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.d.vue.ts +1 -1
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.vue +6 -6
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.vue.d.ts +1 -1
- 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/ScriptInstagramEmbed.d.vue.ts +53 -0
- package/dist/runtime/components/ScriptInstagramEmbed.vue +38 -0
- package/dist/runtime/components/ScriptInstagramEmbed.vue.d.ts +53 -0
- package/dist/runtime/components/ScriptIntercom.vue +4 -3
- package/dist/runtime/components/ScriptLoadingIndicator.d.vue.ts +1 -1
- package/dist/runtime/components/ScriptLoadingIndicator.vue.d.ts +1 -1
- package/dist/runtime/components/ScriptPayPalButtons.d.vue.ts +2 -2
- package/dist/runtime/components/ScriptPayPalButtons.vue +13 -11
- package/dist/runtime/components/ScriptPayPalButtons.vue.d.ts +2 -2
- package/dist/runtime/components/ScriptPayPalMarks.d.vue.ts +2 -2
- package/dist/runtime/components/ScriptPayPalMarks.vue +10 -8
- package/dist/runtime/components/ScriptPayPalMarks.vue.d.ts +2 -2
- package/dist/runtime/components/ScriptPayPalMessages.d.vue.ts +2 -2
- package/dist/runtime/components/ScriptPayPalMessages.vue +11 -9
- package/dist/runtime/components/ScriptPayPalMessages.vue.d.ts +2 -2
- package/dist/runtime/components/ScriptStripePricingTable.vue +2 -2
- package/dist/runtime/components/ScriptVimeoPlayer.vue +1 -1
- package/dist/runtime/components/ScriptXEmbed.d.vue.ts +82 -0
- package/dist/runtime/components/ScriptXEmbed.vue +76 -0
- package/dist/runtime/components/ScriptXEmbed.vue.d.ts +82 -0
- package/dist/runtime/components/ScriptYouTubePlayer.vue +7 -4
- package/dist/runtime/composables/useScript.js +32 -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/composables/useScriptTriggerServiceWorker.d.ts +7 -0
- package/dist/runtime/composables/useScriptTriggerServiceWorker.js +39 -0
- package/dist/runtime/registry/clarity.js +21 -25
- package/dist/runtime/registry/cloudflare-web-analytics.js +1 -1
- package/dist/runtime/registry/crisp.js +1 -1
- package/dist/runtime/registry/databuddy-analytics.js +1 -1
- package/dist/runtime/registry/fathom-analytics.js +1 -1
- package/dist/runtime/registry/google-adsense.js +1 -1
- package/dist/runtime/registry/google-analytics.js +2 -2
- package/dist/runtime/registry/google-maps.d.ts +1 -1
- package/dist/runtime/registry/google-maps.js +1 -1
- package/dist/runtime/registry/google-recaptcha.js +2 -2
- package/dist/runtime/registry/google-sign-in.js +1 -1
- package/dist/runtime/registry/google-tag-manager.d.ts +1 -1
- package/dist/runtime/registry/google-tag-manager.js +2 -2
- package/dist/runtime/registry/hotjar.js +1 -1
- package/dist/runtime/registry/instagram-embed.d.ts +23 -0
- package/dist/runtime/registry/instagram-embed.js +22 -0
- package/dist/runtime/registry/intercom.js +1 -1
- package/dist/runtime/registry/lemon-squeezy.d.ts +0 -1
- package/dist/runtime/registry/matomo-analytics.js +2 -2
- package/dist/runtime/registry/meta-pixel.js +1 -1
- package/dist/runtime/registry/npm.js +1 -1
- package/dist/runtime/registry/paypal.d.ts +1 -1
- package/dist/runtime/registry/paypal.js +2 -2
- package/dist/runtime/registry/plausible-analytics.js +15 -9
- package/dist/runtime/registry/posthog.d.ts +3 -2
- package/dist/runtime/registry/posthog.js +8 -12
- package/dist/runtime/registry/reddit-pixel.js +1 -1
- package/dist/runtime/registry/rybbit-analytics.js +5 -3
- package/dist/runtime/registry/segment.js +1 -1
- package/dist/runtime/registry/snapchat-pixel.js +1 -1
- package/dist/runtime/registry/stripe.d.ts +1 -1
- package/dist/runtime/registry/stripe.js +1 -1
- package/dist/runtime/registry/tiktok-pixel.d.ts +1 -0
- package/dist/runtime/registry/tiktok-pixel.js +2 -1
- package/dist/runtime/registry/umami-analytics.js +1 -1
- 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 +77 -0
- package/dist/runtime/registry/x-embed.js +41 -0
- package/dist/runtime/registry/x-pixel.js +1 -1
- 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/instagram-embed-asset.d.ts +2 -0
- package/dist/runtime/server/instagram-embed-asset.js +42 -0
- package/dist/runtime/server/instagram-embed-image.d.ts +2 -0
- package/dist/runtime/server/instagram-embed-image.js +54 -0
- package/dist/runtime/server/instagram-embed.d.ts +2 -0
- package/dist/runtime/server/instagram-embed.js +91 -0
- package/dist/runtime/server/proxy-handler.d.ts +6 -0
- package/dist/runtime/server/proxy-handler.js +264 -0
- package/dist/runtime/server/utils/privacy.d.ts +141 -0
- package/dist/runtime/server/utils/privacy.js +324 -0
- package/dist/runtime/server/x-embed-image.d.ts +2 -0
- package/dist/runtime/server/x-embed-image.js +53 -0
- package/dist/runtime/server/x-embed.d.ts +49 -0
- package/dist/runtime/server/x-embed.js +31 -0
- package/dist/runtime/types.d.ts +51 -22
- package/dist/runtime/utils/pure.d.ts +9 -0
- package/dist/runtime/utils/pure.js +0 -0
- package/dist/runtime/utils.d.ts +3 -3
- package/dist/runtime/utils.js +3 -2
- package/dist/shared/scripts.DLRgvHQg.mjs +288 -0
- package/dist/stats.d.mts +39 -0
- package/dist/stats.d.ts +39 -0
- package/dist/stats.mjs +711 -0
- package/dist/types.d.mts +1 -1
- package/package.json +48 -41
- package/dist/client/_nuxt/Bdf7Qtwg.js +0 -1
- package/dist/client/_nuxt/CoyZWCgl.js +0 -162
- package/dist/client/_nuxt/Ds1k3yKJ.js +0 -1
- package/dist/client/_nuxt/builds/meta/62574f80-71d4-4f9e-8b96-145c85230d99.json +0 -1
- package/dist/client/_nuxt/entry.BjfcJo5q.css +0 -1
- package/dist/client/_nuxt/error-404.D45Vtjcx.css +0 -1
- package/dist/client/_nuxt/error-500.BOm1rWQf.css +0 -1
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { useRuntimeConfig } from "#imports";
|
|
2
|
+
import { createError, defineEventHandler, getHeaders, getQuery, getRequestIP, getRequestWebStream, readBody, setResponseHeader } from "h3";
|
|
3
|
+
import { useNitroApp } from "nitropack/runtime";
|
|
4
|
+
import {
|
|
5
|
+
anonymizeIP,
|
|
6
|
+
mergePrivacy,
|
|
7
|
+
normalizeLanguage,
|
|
8
|
+
normalizeUserAgent,
|
|
9
|
+
resolvePrivacy,
|
|
10
|
+
SENSITIVE_HEADERS,
|
|
11
|
+
stripPayloadFingerprinting
|
|
12
|
+
} from "./utils/privacy.js";
|
|
13
|
+
function stripQueryFingerprinting(query, privacy) {
|
|
14
|
+
const stripped = stripPayloadFingerprinting(query, privacy);
|
|
15
|
+
const params = new URLSearchParams();
|
|
16
|
+
for (const [key, value] of Object.entries(stripped)) {
|
|
17
|
+
if (value !== void 0 && value !== null) {
|
|
18
|
+
params.set(key, typeof value === "object" ? JSON.stringify(value) : String(value));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return params.toString();
|
|
22
|
+
}
|
|
23
|
+
export default defineEventHandler(async (event) => {
|
|
24
|
+
const config = useRuntimeConfig();
|
|
25
|
+
const nitro = useNitroApp();
|
|
26
|
+
const proxyConfig = config["nuxt-scripts-proxy"];
|
|
27
|
+
if (!proxyConfig) {
|
|
28
|
+
throw createError({
|
|
29
|
+
statusCode: 500,
|
|
30
|
+
statusMessage: "First-party proxy not configured"
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const { routes, privacy: globalPrivacy, routePrivacy, debug = import.meta.dev } = proxyConfig;
|
|
34
|
+
const path = event.path;
|
|
35
|
+
const log = debug ? (message, ...args) => {
|
|
36
|
+
console.debug(message, ...args);
|
|
37
|
+
} : () => {
|
|
38
|
+
};
|
|
39
|
+
let targetBase;
|
|
40
|
+
let matchedPrefix;
|
|
41
|
+
let matchedRoutePattern;
|
|
42
|
+
const sortedRoutes = Object.entries(routes).sort((a, b) => b[0].length - a[0].length);
|
|
43
|
+
for (const [routePattern, target] of sortedRoutes) {
|
|
44
|
+
const prefix = routePattern.replace(/\/\*\*$/, "");
|
|
45
|
+
if (path.startsWith(prefix)) {
|
|
46
|
+
targetBase = target.replace(/\/\*\*$/, "");
|
|
47
|
+
matchedPrefix = prefix;
|
|
48
|
+
matchedRoutePattern = routePattern;
|
|
49
|
+
log("[proxy] Matched:", prefix, "->", targetBase);
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (!targetBase || !matchedPrefix || !matchedRoutePattern) {
|
|
54
|
+
log("[proxy] No match for path:", path);
|
|
55
|
+
throw createError({
|
|
56
|
+
statusCode: 404,
|
|
57
|
+
statusMessage: "No proxy route matched",
|
|
58
|
+
message: `No proxy target found for path: ${path}`
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const perScriptInput = routePrivacy[matchedRoutePattern];
|
|
62
|
+
if (debug && perScriptInput === void 0) {
|
|
63
|
+
log("[proxy] WARNING: No privacy config for route", matchedRoutePattern, "\u2014 defaulting to full anonymization");
|
|
64
|
+
}
|
|
65
|
+
const perScriptResolved = resolvePrivacy(perScriptInput ?? true);
|
|
66
|
+
const privacy = globalPrivacy !== void 0 ? mergePrivacy(perScriptResolved, globalPrivacy) : perScriptResolved;
|
|
67
|
+
const anyPrivacy = privacy.ip || privacy.userAgent || privacy.language || privacy.screen || privacy.timezone || privacy.hardware;
|
|
68
|
+
const originalHeaders = getHeaders(event);
|
|
69
|
+
const contentType = originalHeaders["content-type"] || "";
|
|
70
|
+
const compressionParam = new URL(event.path, "http://localhost").searchParams.get("compression");
|
|
71
|
+
const isBinaryBody = Boolean(
|
|
72
|
+
originalHeaders["content-encoding"] || contentType.includes("octet-stream") || compressionParam && /gzip|deflate|br|compress|base64/i.test(compressionParam)
|
|
73
|
+
);
|
|
74
|
+
let targetPath = path.slice(matchedPrefix.length);
|
|
75
|
+
if (targetPath && !targetPath.startsWith("/")) {
|
|
76
|
+
targetPath = `/${targetPath}`;
|
|
77
|
+
}
|
|
78
|
+
let targetUrl = targetBase + targetPath;
|
|
79
|
+
if (anyPrivacy) {
|
|
80
|
+
const query = getQuery(event);
|
|
81
|
+
if (Object.keys(query).length > 0) {
|
|
82
|
+
const strippedQuery = stripQueryFingerprinting(query, privacy);
|
|
83
|
+
const basePath = targetUrl.split("?")[0] || targetUrl;
|
|
84
|
+
targetUrl = strippedQuery ? `${basePath}?${strippedQuery}` : basePath;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const headers = {};
|
|
88
|
+
for (const [key, value] of Object.entries(originalHeaders)) {
|
|
89
|
+
if (!value)
|
|
90
|
+
continue;
|
|
91
|
+
const lowerKey = key.toLowerCase();
|
|
92
|
+
if (SENSITIVE_HEADERS.includes(lowerKey))
|
|
93
|
+
continue;
|
|
94
|
+
if (lowerKey === "content-length") {
|
|
95
|
+
if (anyPrivacy && !isBinaryBody)
|
|
96
|
+
continue;
|
|
97
|
+
headers[lowerKey] = value;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
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") {
|
|
101
|
+
if (privacy.ip)
|
|
102
|
+
continue;
|
|
103
|
+
headers[lowerKey] = value;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (lowerKey === "user-agent") {
|
|
107
|
+
headers[key] = privacy.userAgent ? normalizeUserAgent(value) : value;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (lowerKey === "accept-language") {
|
|
111
|
+
headers[key] = privacy.language ? normalizeLanguage(value) : value;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (lowerKey === "sec-ch-ua" || lowerKey === "sec-ch-ua-full-version-list") {
|
|
115
|
+
headers[lowerKey] = privacy.hardware ? value.replace(/;v="(\d+)\.[^"]*"/g, ';v="$1"') : value;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (lowerKey === "sec-ch-ua-platform-version" || lowerKey === "sec-ch-ua-arch" || lowerKey === "sec-ch-ua-model" || lowerKey === "sec-ch-ua-bitness") {
|
|
119
|
+
if (privacy.hardware)
|
|
120
|
+
continue;
|
|
121
|
+
headers[lowerKey] = value;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
headers[key] = value;
|
|
125
|
+
}
|
|
126
|
+
if (!headers["x-forwarded-for"]) {
|
|
127
|
+
const clientIP = getRequestIP(event, { xForwardedFor: true });
|
|
128
|
+
if (clientIP) {
|
|
129
|
+
if (privacy.ip) {
|
|
130
|
+
headers["x-forwarded-for"] = anonymizeIP(clientIP);
|
|
131
|
+
} else {
|
|
132
|
+
headers["x-forwarded-for"] = clientIP;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
} else if (privacy.ip) {
|
|
136
|
+
headers["x-forwarded-for"] = headers["x-forwarded-for"].split(",").map((ip) => anonymizeIP(ip.trim())).join(", ");
|
|
137
|
+
}
|
|
138
|
+
let body;
|
|
139
|
+
let rawBody;
|
|
140
|
+
let passthroughBody = false;
|
|
141
|
+
const method = event.method?.toUpperCase();
|
|
142
|
+
const originalQuery = getQuery(event);
|
|
143
|
+
const isWriteMethod = method === "POST" || method === "PUT" || method === "PATCH";
|
|
144
|
+
if (isWriteMethod) {
|
|
145
|
+
if (isBinaryBody || !anyPrivacy) {
|
|
146
|
+
passthroughBody = true;
|
|
147
|
+
} else {
|
|
148
|
+
rawBody = await readBody(event);
|
|
149
|
+
if (rawBody != null) {
|
|
150
|
+
if (Array.isArray(rawBody)) {
|
|
151
|
+
body = rawBody.map(
|
|
152
|
+
(item) => item && typeof item === "object" && !Array.isArray(item) ? stripPayloadFingerprinting(item, privacy) : item
|
|
153
|
+
);
|
|
154
|
+
} else if (typeof rawBody === "object") {
|
|
155
|
+
body = stripPayloadFingerprinting(rawBody, privacy);
|
|
156
|
+
} else if (typeof rawBody === "string") {
|
|
157
|
+
if (rawBody.startsWith("{") || rawBody.startsWith("[")) {
|
|
158
|
+
let parsed = null;
|
|
159
|
+
try {
|
|
160
|
+
parsed = JSON.parse(rawBody);
|
|
161
|
+
} catch {
|
|
162
|
+
}
|
|
163
|
+
if (Array.isArray(parsed)) {
|
|
164
|
+
body = parsed.map(
|
|
165
|
+
(item) => item && typeof item === "object" && !Array.isArray(item) ? stripPayloadFingerprinting(item, privacy) : item
|
|
166
|
+
);
|
|
167
|
+
} else if (parsed && typeof parsed === "object") {
|
|
168
|
+
body = stripPayloadFingerprinting(parsed, privacy);
|
|
169
|
+
} else {
|
|
170
|
+
body = rawBody;
|
|
171
|
+
}
|
|
172
|
+
} else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
173
|
+
const params = new URLSearchParams(rawBody);
|
|
174
|
+
const obj = {};
|
|
175
|
+
params.forEach((value, key) => {
|
|
176
|
+
obj[key] = value;
|
|
177
|
+
});
|
|
178
|
+
const stripped = stripPayloadFingerprinting(obj, privacy);
|
|
179
|
+
const stringified = {};
|
|
180
|
+
for (const [k, v] of Object.entries(stripped)) {
|
|
181
|
+
if (v === void 0 || v === null)
|
|
182
|
+
continue;
|
|
183
|
+
stringified[k] = typeof v === "string" ? v : JSON.stringify(v);
|
|
184
|
+
}
|
|
185
|
+
body = new URLSearchParams(stringified).toString();
|
|
186
|
+
} else {
|
|
187
|
+
body = rawBody;
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
body = rawBody;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
await nitro.hooks.callHook("nuxt-scripts:proxy", {
|
|
196
|
+
timestamp: Date.now(),
|
|
197
|
+
path: event.path,
|
|
198
|
+
targetUrl,
|
|
199
|
+
method: method || "GET",
|
|
200
|
+
privacy,
|
|
201
|
+
passthroughBody,
|
|
202
|
+
original: {
|
|
203
|
+
headers: { ...originalHeaders },
|
|
204
|
+
query: originalQuery,
|
|
205
|
+
body: passthroughBody ? "<passthrough>" : rawBody ?? null
|
|
206
|
+
},
|
|
207
|
+
stripped: {
|
|
208
|
+
headers,
|
|
209
|
+
query: anyPrivacy ? stripPayloadFingerprinting(originalQuery, privacy) : originalQuery,
|
|
210
|
+
body: passthroughBody ? "<passthrough>" : body ?? null
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
log("[proxy] Fetching:", targetUrl);
|
|
214
|
+
const controller = new AbortController();
|
|
215
|
+
const timeoutId = setTimeout(() => controller.abort(), 15e3);
|
|
216
|
+
let fetchBody;
|
|
217
|
+
if (passthroughBody) {
|
|
218
|
+
fetchBody = getRequestWebStream(event);
|
|
219
|
+
} else if (body !== void 0) {
|
|
220
|
+
fetchBody = typeof body === "string" ? body : JSON.stringify(body);
|
|
221
|
+
}
|
|
222
|
+
let response;
|
|
223
|
+
try {
|
|
224
|
+
response = await fetch(targetUrl, {
|
|
225
|
+
method: method || "GET",
|
|
226
|
+
headers,
|
|
227
|
+
body: fetchBody,
|
|
228
|
+
credentials: "omit",
|
|
229
|
+
// Don't send cookies to third parties
|
|
230
|
+
signal: controller.signal,
|
|
231
|
+
// @ts-expect-error Node fetch supports duplex for streaming request bodies
|
|
232
|
+
duplex: passthroughBody ? "half" : void 0
|
|
233
|
+
});
|
|
234
|
+
} catch (err) {
|
|
235
|
+
clearTimeout(timeoutId);
|
|
236
|
+
log("[proxy] Fetch error:", err instanceof Error ? err.message : err);
|
|
237
|
+
if (path.includes("/collect") || path.includes("/tr") || path.includes("/events")) {
|
|
238
|
+
event.node.res.statusCode = 204;
|
|
239
|
+
return "";
|
|
240
|
+
}
|
|
241
|
+
const isTimeout = err instanceof Error && (err.message.includes("aborted") || err.message.includes("timeout"));
|
|
242
|
+
throw createError({
|
|
243
|
+
statusCode: isTimeout ? 504 : 502,
|
|
244
|
+
statusMessage: isTimeout ? "Upstream timeout" : "Bad Gateway",
|
|
245
|
+
message: "Failed to reach upstream"
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
clearTimeout(timeoutId);
|
|
249
|
+
log("[proxy] Response:", response.status, response.statusText);
|
|
250
|
+
const skipHeaders = ["set-cookie", "transfer-encoding", "content-encoding", "content-length"];
|
|
251
|
+
response.headers.forEach((value, key) => {
|
|
252
|
+
if (!skipHeaders.includes(key.toLowerCase())) {
|
|
253
|
+
setResponseHeader(event, key, value);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
event.node.res.statusCode = response.status;
|
|
257
|
+
event.node.res.statusMessage = response.statusText;
|
|
258
|
+
const responseContentType = response.headers.get("content-type") || "";
|
|
259
|
+
const isTextContent = responseContentType.includes("text") || responseContentType.includes("javascript") || responseContentType.includes("json");
|
|
260
|
+
if (isTextContent) {
|
|
261
|
+
return await response.text();
|
|
262
|
+
}
|
|
263
|
+
return Buffer.from(await response.arrayBuffer());
|
|
264
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
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;
|
|
42
|
+
/**
|
|
43
|
+
* Headers that reveal user IP address - stripped in proxy mode,
|
|
44
|
+
* anonymized in anonymize mode.
|
|
45
|
+
*/
|
|
46
|
+
export declare const IP_HEADERS: string[];
|
|
47
|
+
/**
|
|
48
|
+
* Headers that enable fingerprinting - normalized in anonymize mode.
|
|
49
|
+
*/
|
|
50
|
+
export declare const FINGERPRINT_HEADERS: string[];
|
|
51
|
+
/**
|
|
52
|
+
* Sensitive headers that should never be forwarded to third parties.
|
|
53
|
+
*/
|
|
54
|
+
export declare const SENSITIVE_HEADERS: string[];
|
|
55
|
+
/**
|
|
56
|
+
* Payload parameters relevant to privacy.
|
|
57
|
+
*
|
|
58
|
+
* Note: userId and userData are intentionally NOT modified by stripPayloadFingerprinting.
|
|
59
|
+
* Analytics services require user identifiers (cid, uid, fbp, etc.) and user data (ud, email)
|
|
60
|
+
* to function correctly. These are listed here for documentation and param-detection tests only.
|
|
61
|
+
* The privacy model anonymizes device/browser fingerprinting while preserving user-level analytics IDs.
|
|
62
|
+
*/
|
|
63
|
+
export declare const STRIP_PARAMS: {
|
|
64
|
+
ip: string[];
|
|
65
|
+
userId: string[];
|
|
66
|
+
userData: string[];
|
|
67
|
+
screen: string[];
|
|
68
|
+
hardware: string[];
|
|
69
|
+
platform: string[];
|
|
70
|
+
version: string[];
|
|
71
|
+
browserVersion: string[];
|
|
72
|
+
browserData: string[];
|
|
73
|
+
location: string[];
|
|
74
|
+
canvas: string[];
|
|
75
|
+
deviceInfo: string[];
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Parameters that should be normalized (not stripped).
|
|
79
|
+
*/
|
|
80
|
+
export declare const NORMALIZE_PARAMS: {
|
|
81
|
+
language: string[];
|
|
82
|
+
userAgent: string[];
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Anonymize an IP address by zeroing trailing segments.
|
|
86
|
+
*/
|
|
87
|
+
export declare function anonymizeIP(ip: string): string;
|
|
88
|
+
/**
|
|
89
|
+
* Normalize User-Agent to browser family and major version only.
|
|
90
|
+
*/
|
|
91
|
+
export declare function normalizeUserAgent(ua: string): string;
|
|
92
|
+
/**
|
|
93
|
+
* Normalize Accept-Language to primary language tag (preserving country).
|
|
94
|
+
* "en-US,en;q=0.9,fr;q=0.8" → "en-US"
|
|
95
|
+
*/
|
|
96
|
+
export declare function normalizeLanguage(lang: string): string;
|
|
97
|
+
/**
|
|
98
|
+
* Generalize screen resolution to 3 coarse device-class buckets (mobile / tablet / desktop).
|
|
99
|
+
* Handles both combined "WxH" strings and individual dimension values (sh, sw).
|
|
100
|
+
*
|
|
101
|
+
* When `dimension` is specified, uses the correct bucket for that axis:
|
|
102
|
+
* - 'width': [1920, 768, 360]
|
|
103
|
+
* - 'height': [1080, 1024, 640]
|
|
104
|
+
* Without `dimension`, individual values use width thresholds (backward-compatible).
|
|
105
|
+
*/
|
|
106
|
+
export declare function generalizeScreen(value: unknown, dimension?: 'width' | 'height'): string | number;
|
|
107
|
+
/**
|
|
108
|
+
* Generalize hardware concurrency / device memory to common bucket.
|
|
109
|
+
*/
|
|
110
|
+
export declare function generalizeHardware(value: unknown): number;
|
|
111
|
+
/**
|
|
112
|
+
* Generalize a version string to major version, preserving the original format.
|
|
113
|
+
* "6.17.0" → "6.0.0", "143.0.7499.4" → "143.0.0.0"
|
|
114
|
+
*/
|
|
115
|
+
export declare function generalizeVersion(value: unknown): string;
|
|
116
|
+
/**
|
|
117
|
+
* Generalize browser version list to major versions, preserving segment count.
|
|
118
|
+
* Handles Snapchat d_bvs format: [,{"brand":"Chrome","version":"143.0.7499.4"}...]
|
|
119
|
+
* Handles GA uafvl format: HeadlessChrome;143.0.7499.4|Chromium;143.0.7499.4|...
|
|
120
|
+
*/
|
|
121
|
+
export declare function generalizeBrowserVersions(value: unknown): string;
|
|
122
|
+
/**
|
|
123
|
+
* Generalize timezone to reduce precision.
|
|
124
|
+
* IANA names → UTC offset string, numeric offsets → bucketed to 3-hour intervals.
|
|
125
|
+
*/
|
|
126
|
+
export declare function generalizeTimezone(value: unknown): string | number;
|
|
127
|
+
/**
|
|
128
|
+
* Anonymize a combined device-info string (e.g. X/Twitter `dv` param).
|
|
129
|
+
* Parses the delimited string and generalizes fingerprinting components
|
|
130
|
+
* (timezone, language, screen dimensions) while keeping low-entropy values.
|
|
131
|
+
*/
|
|
132
|
+
export declare function anonymizeDeviceInfo(value: string): string;
|
|
133
|
+
/**
|
|
134
|
+
* Recursively anonymize fingerprinting data in payload.
|
|
135
|
+
* Fields are generalized or normalized rather than stripped, so endpoints
|
|
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.
|
|
140
|
+
*/
|
|
141
|
+
export declare function stripPayloadFingerprinting(payload: Record<string, unknown>, privacy?: ResolvedProxyPrivacy): Record<string, unknown>;
|