@nuxt/scripts 1.0.0-beta.6 → 1.0.0-rc.1
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 -0
- package/dist/devtools-client/404.html +1 -0
- package/dist/devtools-client/_fonts/4ppnHhMi-pBsWSPo7mY0avYxlDoAg1N3PTzCwXLZ5rA-d9oibkGnTd1JL3tc_xnaVgBLYmOB8kjrK2cvZaqwj9s.woff2 +0 -0
- package/dist/devtools-client/_fonts/4qBuU9MRVUlPZNPSF7Xom_sK8RBEnfYu-9VXFrdq8A8-8TDwLE1HAj1sQn7XxVWtM_7sIaPM-DTdO3Pf8U2DF1U.woff2 +0 -0
- package/dist/devtools-client/_fonts/6dYsbWUd_BpKJ7mdDihgOcya1gHXLpJBuMYXux3WMjE-q3fYNS8YbW5n7ZeXI2vSNgkRWW5VDPKAl51SNTjG2qk.woff2 +0 -0
- package/dist/devtools-client/_fonts/Lr-hqqZZsYmCt0ITUlr1CUrWim9fsKvoDFZliMxgNHY-iTa_Yt_PzhOY9TX7ZXdSlEPim6iRt92xhECwaxWxd5w.woff2 +0 -0
- package/dist/devtools-client/_fonts/OknHvWI6KtYn1JQBzX7eSpNDBQ8520F9TvSUJYkVf6A-xeZn9253svK_8Q2LD0XEruY_MnEsuCRO5LenPoggC0Y.woff2 +0 -0
- package/dist/devtools-client/_fonts/PV2hrQG6wq5BlIPDjdL1IcOflycaghyt5MHzlBqZtlo-lb_WexLz3VZqfTN0oi554iBH5tT2j2UFEV-XErCAS3E.woff2 +0 -0
- package/dist/devtools-client/_fonts/UA7OtwYHwGN_HjcVGTdmiQxUit7FlqkCwxVUWSeXVnQ-B4OXCFOL_tWrYODpQTc07aMaj0c2cewTOmBRWR9tD-A.woff2 +0 -0
- package/dist/devtools-client/_fonts/VE4cDVCv5MxbFM7ZLoLCGbIpNd71zhp7MDI9lmN5Y7I-xZyDYCUVrd6LV8eVGF3Um3UZjBFuUtDGtvdyTBBRYBo.woff2 +0 -0
- package/dist/devtools-client/_fonts/fVoGbnMbBFd5L9BBp9fUPavUSkZ_EmsQNSyadkT-108-U4T0khaeLQSIhtt9eVvaCEKJjtWJ4ioRJOf8hvqkWY0.woff2 +0 -0
- package/dist/devtools-client/_fonts/lQAxeCEs1R0Lw-H9XRU1RlOARQN8J6npRsPjyEDMe5s-_DUSLEkO3tKTuun_gSnDLoQPVEnpOnyqZMOw0ByZ6PA.woff2 +0 -0
- package/dist/devtools-client/_fonts/lntlqNHKLV2n82yTwMde70QqOjcfLE2XJ5oKZ3vRPWc-z6TxpIZQdWXztWLr9_OFWqt_WJJoeGtuK_-XQMZGQwE.woff2 +0 -0
- package/dist/devtools-client/_fonts/qxAYvKsXWeYv731eb-h5TRurcdIP_W44mpNdX-HABAk-zUDeMEFlNtNbrwvT9JxLEBg0TphGy70O6RfIoIX_ZwU.woff2 +0 -0
- package/dist/devtools-client/_nuxt/B3kN3DAy.js +1 -0
- package/dist/devtools-client/_nuxt/B8PEiB0p.js +1 -0
- package/dist/devtools-client/_nuxt/C8jhSQ8l.js +1 -0
- package/dist/devtools-client/_nuxt/CJD6wrkT.js +188 -0
- package/dist/devtools-client/_nuxt/CfOsp0mU.js +1 -0
- package/dist/devtools-client/_nuxt/DKL6PHO3.js +1 -0
- package/dist/devtools-client/_nuxt/ajngqPCs.js +1 -0
- package/dist/devtools-client/_nuxt/builds/latest.json +1 -0
- package/dist/devtools-client/_nuxt/builds/meta/b800a0be-5cab-4ea6-89e3-dd3a85690a73.json +1 -0
- package/dist/devtools-client/_nuxt/dlaR8P-P.js +1 -0
- package/dist/devtools-client/_nuxt/entry.BwpOBArY.css +1 -0
- package/dist/devtools-client/_nuxt/error-404.CvOVjXeC.css +1 -0
- package/dist/devtools-client/_nuxt/error-500.BIm53nmx.css +1 -0
- package/dist/devtools-client/_nuxt/first-party.C8Ha4JLM.css +1 -0
- package/dist/devtools-client/_nuxt/index.CA-OpSj0.css +1 -0
- package/dist/devtools-client/_nuxt/registry.B9lnjF_b.css +1 -0
- package/dist/devtools-client/_nuxt/wDzz0qaB.js +1 -0
- package/dist/devtools-client/docs/index.html +1 -0
- package/dist/devtools-client/first-party/index.html +1 -0
- package/dist/devtools-client/index.html +1 -0
- package/dist/devtools-client/registry/index.html +1 -0
- package/dist/module.d.mts +29 -84
- package/dist/module.d.ts +121 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +941 -757
- package/dist/registry.d.mts +91 -4
- package/dist/registry.d.ts +93 -0
- package/dist/registry.mjs +669 -324
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.d.vue.ts +15 -79
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.vue +78 -180
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.vue.d.ts +15 -79
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.d.vue.ts +6 -55
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue +12 -83
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsAdvancedMarkerElement.vue.d.ts +6 -55
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsCircle.d.vue.ts +5 -1
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsCircle.vue +24 -38
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsCircle.vue.d.ts +5 -1
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsGeoJson.d.vue.ts +43 -0
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsGeoJson.vue +60 -0
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsGeoJson.vue.d.ts +43 -0
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsHeatmapLayer.d.vue.ts +4 -0
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsHeatmapLayer.vue +22 -26
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsHeatmapLayer.vue.d.ts +4 -0
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.d.vue.ts +9 -5
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue +56 -57
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsInfoWindow.vue.d.ts +9 -5
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarker.d.vue.ts +24 -41
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarker.vue +69 -73
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarker.vue.d.ts +24 -41
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.d.vue.ts +36 -4
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue +82 -37
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue.d.ts +36 -4
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.d.vue.ts +78 -0
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue +222 -0
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue.d.ts +78 -0
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPinElement.d.vue.ts +10 -3
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPinElement.vue +9 -41
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPinElement.vue.d.ts +10 -3
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPolygon.d.vue.ts +7 -3
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPolygon.vue +23 -38
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPolygon.vue.d.ts +7 -3
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPolyline.d.vue.ts +7 -3
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPolyline.vue +23 -38
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsPolyline.vue.d.ts +7 -3
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsRectangle.d.vue.ts +7 -3
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsRectangle.vue +24 -38
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsRectangle.vue.d.ts +7 -3
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.d.vue.ts +200 -0
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.vue +165 -0
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.vue.d.ts +200 -0
- package/dist/runtime/components/GoogleMaps/bindGoogleMapsEvents.d.ts +13 -0
- package/dist/runtime/components/GoogleMaps/bindGoogleMapsEvents.js +8 -0
- package/dist/runtime/components/GoogleMaps/injectionKeys.d.ts +10 -0
- package/dist/runtime/components/GoogleMaps/injectionKeys.js +2 -0
- package/dist/runtime/components/GoogleMaps/useGoogleMapsResource.d.ts +48 -0
- package/dist/runtime/components/GoogleMaps/useGoogleMapsResource.js +51 -0
- package/dist/runtime/components/ScriptBlueskyEmbed.d.vue.ts +85 -0
- package/dist/runtime/components/ScriptBlueskyEmbed.vue +88 -0
- package/dist/runtime/components/ScriptBlueskyEmbed.vue.d.ts +85 -0
- 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.d.vue.ts +1 -2
- package/dist/runtime/components/ScriptInstagramEmbed.vue +8 -3
- package/dist/runtime/components/ScriptInstagramEmbed.vue.d.ts +1 -2
- 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 +0 -2
- package/dist/runtime/components/ScriptXEmbed.vue +14 -8
- package/dist/runtime/components/ScriptXEmbed.vue.d.ts +0 -2
- 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 +148 -8
- package/dist/runtime/composables/useScriptEventPage.js +2 -2
- package/dist/runtime/composables/useScriptTriggerConsent.d.ts +10 -0
- package/dist/runtime/composables/useScriptTriggerConsent.js +33 -20
- package/dist/runtime/composables/useScriptTriggerElement.js +1 -1
- package/dist/runtime/composables/useScriptTriggerIdleTimeout.js +1 -1
- package/dist/runtime/devtools-standalone-bridge.client.d.ts +8 -0
- package/dist/runtime/devtools-standalone-bridge.client.js +50 -0
- package/dist/runtime/registry/bing-uet.d.ts +20 -0
- package/dist/runtime/registry/bing-uet.js +29 -0
- package/dist/runtime/registry/bluesky-embed.d.ts +116 -0
- package/dist/runtime/registry/bluesky-embed.js +72 -0
- 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 +10 -40
- 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 +7 -26
- package/dist/runtime/registry/fathom-analytics.js +3 -25
- package/dist/runtime/registry/google-adsense.d.ts +3 -11
- 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 +26 -0
- package/dist/runtime/registry/gravatar.js +36 -0
- package/dist/runtime/registry/hotjar.d.ts +4 -6
- package/dist/runtime/registry/hotjar.js +2 -5
- package/dist/runtime/registry/instagram-embed.d.ts +3 -18
- package/dist/runtime/registry/instagram-embed.js +4 -19
- package/dist/runtime/registry/intercom.d.ts +5 -13
- package/dist/runtime/registry/intercom.js +2 -12
- package/dist/runtime/registry/matomo-analytics.d.ts +3 -12
- package/dist/runtime/registry/matomo-analytics.js +3 -12
- package/dist/runtime/registry/meta-pixel.d.ts +4 -6
- package/dist/runtime/registry/meta-pixel.js +2 -4
- package/dist/runtime/registry/mixpanel-analytics.d.ts +22 -0
- package/dist/runtime/registry/mixpanel-analytics.js +46 -0
- package/dist/runtime/registry/npm.d.ts +3 -7
- 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 +19 -14
- package/dist/runtime/registry/posthog.d.ts +10 -12
- package/dist/runtime/registry/posthog.js +4 -13
- package/dist/runtime/registry/reddit-pixel.d.ts +5 -6
- 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 +10 -20
- package/dist/runtime/registry/schemas.d.ts +982 -0
- package/dist/runtime/registry/schemas.js +937 -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 +5 -34
- 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 +4 -7
- 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 +3 -17
- package/dist/runtime/registry/x-embed.js +3 -18
- package/dist/runtime/registry/x-pixel.d.ts +4 -7
- 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/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 +2 -13
- package/dist/runtime/server/gravatar-proxy.d.ts +2 -0
- package/dist/runtime/server/gravatar-proxy.js +46 -0
- package/dist/runtime/server/instagram-embed-asset.js +8 -41
- package/dist/runtime/server/instagram-embed-image.js +6 -53
- package/dist/runtime/server/instagram-embed.d.ts +16 -0
- package/dist/runtime/server/instagram-embed.js +176 -35
- package/dist/runtime/server/proxy-handler.js +142 -95
- 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/utils/privacy.d.ts +1 -10
- package/dist/runtime/server/utils/privacy.js +60 -40
- package/dist/runtime/server/x-embed-image.js +5 -49
- package/dist/runtime/server/x-embed.js +3 -2
- package/dist/runtime/types.d.ts +272 -51
- package/dist/runtime/types.js +0 -2
- package/dist/runtime/utils/pure.d.ts +1 -1
- package/dist/runtime/utils.d.ts +6 -4
- package/dist/runtime/utils.js +31 -14
- package/dist/stats.d.mts +202 -0
- package/dist/stats.d.ts +202 -0
- package/dist/stats.mjs +3875 -0
- package/dist/types-source.d.mts +17 -0
- package/dist/types-source.d.ts +17 -0
- package/dist/types-source.mjs +3414 -0
- package/dist/types.d.mts +4 -2
- package/package.json +35 -61
- package/README.md +0 -86
- package/dist/client/200.html +0 -1
- package/dist/client/404.html +0 -1
- package/dist/client/_nuxt/BPQ3VLAy.js +0 -1
- package/dist/client/_nuxt/BpR-tlZc.js +0 -1
- package/dist/client/_nuxt/CBbMDhE2.js +0 -162
- package/dist/client/_nuxt/CEMAW3aB.js +0 -1
- package/dist/client/_nuxt/MWkREqzj.js +0 -1
- package/dist/client/_nuxt/builds/latest.json +0 -1
- package/dist/client/_nuxt/builds/meta/762d443a-0880-424f-bda8-2b32b39d43ec.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/client/index.html +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/validation/mock.d.ts +0 -42
- package/dist/runtime/validation/mock.js +0 -21
- package/dist/runtime/validation/valibot.d.ts +0 -1
- package/dist/runtime/validation/valibot.js +0 -1
- /package/dist/{client → devtools-client}/_nuxt/CVO1_9PV.js +0 -0
- /package/dist/{client → devtools-client}/_nuxt/Cp-IABpG.js +0 -0
- /package/dist/{client → devtools-client}/_nuxt/D0r3Knsf.js +0 -0
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import { defineEventHandler, getHeaders, getRequestIP, readBody, getQuery, setResponseHeader, createError } from "h3";
|
|
2
1
|
import { useRuntimeConfig } from "#imports";
|
|
2
|
+
import { createError, defineEventHandler, getHeaders, getQuery, getRequestIP, getRequestWebStream, readBody, setResponseHeader } from "h3";
|
|
3
3
|
import { useNitroApp } from "nitropack/runtime";
|
|
4
4
|
import {
|
|
5
|
-
SENSITIVE_HEADERS,
|
|
6
5
|
anonymizeIP,
|
|
6
|
+
mergePrivacy,
|
|
7
7
|
normalizeLanguage,
|
|
8
8
|
normalizeUserAgent,
|
|
9
|
-
stripPayloadFingerprinting,
|
|
10
9
|
resolvePrivacy,
|
|
11
|
-
|
|
10
|
+
SENSITIVE_HEADERS,
|
|
11
|
+
stripPayloadFingerprinting
|
|
12
12
|
} from "./utils/privacy.js";
|
|
13
|
+
const COMPRESSION_RE = /gzip|deflate|br|compress|base64/i;
|
|
14
|
+
const CLIENT_HINT_VERSION_RE = /;v="(\d+)\.[^"]*"/g;
|
|
15
|
+
const SKIP_RESPONSE_HEADERS = /* @__PURE__ */ new Set(["set-cookie", "transfer-encoding", "content-encoding", "content-length"]);
|
|
13
16
|
function stripQueryFingerprinting(query, privacy) {
|
|
14
17
|
const stripped = stripPayloadFingerprinting(query, privacy);
|
|
15
18
|
const params = new URLSearchParams();
|
|
@@ -18,11 +21,10 @@ function stripQueryFingerprinting(query, privacy) {
|
|
|
18
21
|
params.set(key, typeof value === "object" ? JSON.stringify(value) : String(value));
|
|
19
22
|
}
|
|
20
23
|
}
|
|
21
|
-
return params.toString();
|
|
24
|
+
return { queryString: params.toString(), stripped };
|
|
22
25
|
}
|
|
23
26
|
export default defineEventHandler(async (event) => {
|
|
24
27
|
const config = useRuntimeConfig();
|
|
25
|
-
const nitro = useNitroApp();
|
|
26
28
|
const proxyConfig = config["nuxt-scripts-proxy"];
|
|
27
29
|
if (!proxyConfig) {
|
|
28
30
|
throw createError({
|
|
@@ -30,63 +32,79 @@ export default defineEventHandler(async (event) => {
|
|
|
30
32
|
statusMessage: "First-party proxy not configured"
|
|
31
33
|
});
|
|
32
34
|
}
|
|
33
|
-
const {
|
|
35
|
+
const { proxyPrefix, domainPrivacy, privacy: globalPrivacy, debug = import.meta.dev } = proxyConfig;
|
|
34
36
|
const path = event.path;
|
|
35
37
|
const log = debug ? (message, ...args) => {
|
|
36
38
|
console.debug(message, ...args);
|
|
37
39
|
} : () => {
|
|
38
40
|
};
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
const afterPrefix = path.slice(proxyPrefix.length + 1);
|
|
42
|
+
const slashIdx = afterPrefix.indexOf("/");
|
|
43
|
+
const domain = slashIdx > 0 ? afterPrefix.slice(0, slashIdx) : afterPrefix;
|
|
44
|
+
const remainingPath = slashIdx > 0 ? afterPrefix.slice(slashIdx) : "/";
|
|
45
|
+
if (!domain) {
|
|
46
|
+
log("[proxy] No domain in path:", path);
|
|
47
|
+
throw createError({
|
|
48
|
+
statusCode: 404,
|
|
49
|
+
statusMessage: "No proxy domain found",
|
|
50
|
+
message: `No domain in proxy path: ${path}`
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
let perScriptInput;
|
|
54
|
+
for (const [configDomain, privacyInput] of Object.entries(domainPrivacy)) {
|
|
55
|
+
if (domain === configDomain || domain.endsWith(`.${configDomain}`)) {
|
|
56
|
+
perScriptInput = privacyInput;
|
|
50
57
|
break;
|
|
51
58
|
}
|
|
52
59
|
}
|
|
53
|
-
if (
|
|
54
|
-
log("[proxy]
|
|
60
|
+
if (perScriptInput === void 0) {
|
|
61
|
+
log("[proxy] Rejected: domain not in allowlist:", domain);
|
|
55
62
|
throw createError({
|
|
56
|
-
statusCode:
|
|
57
|
-
statusMessage: "
|
|
58
|
-
message: `
|
|
63
|
+
statusCode: 403,
|
|
64
|
+
statusMessage: "Domain not allowed",
|
|
65
|
+
message: `Proxy domain not in allowlist: ${domain}`
|
|
59
66
|
});
|
|
60
67
|
}
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
log("[proxy] WARNING: No privacy config for route", matchedRoutePattern, "\u2014 defaulting to full anonymization");
|
|
64
|
-
}
|
|
68
|
+
const targetBase = `https://${domain}`;
|
|
69
|
+
log("[proxy] Matched:", domain, "->", targetBase);
|
|
65
70
|
const perScriptResolved = resolvePrivacy(perScriptInput ?? true);
|
|
66
71
|
const privacy = globalPrivacy !== void 0 ? mergePrivacy(perScriptResolved, globalPrivacy) : perScriptResolved;
|
|
67
72
|
const anyPrivacy = privacy.ip || privacy.userAgent || privacy.language || privacy.screen || privacy.timezone || privacy.hardware;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
const originalHeaders = getHeaders(event);
|
|
74
|
+
const originalQuery = getQuery(event);
|
|
75
|
+
const contentType = originalHeaders["content-type"] || "";
|
|
76
|
+
const compressionParam = originalQuery.compression || "";
|
|
77
|
+
const isBinaryBody = Boolean(
|
|
78
|
+
originalHeaders["content-encoding"] || contentType.includes("octet-stream") || compressionParam && COMPRESSION_RE.test(compressionParam)
|
|
79
|
+
);
|
|
80
|
+
let targetUrl = targetBase + remainingPath;
|
|
81
|
+
let strippedQueryRecord;
|
|
73
82
|
if (anyPrivacy) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
if (Object.keys(originalQuery).length > 0) {
|
|
84
|
+
const { queryString, stripped } = stripQueryFingerprinting(originalQuery, privacy);
|
|
85
|
+
strippedQueryRecord = stripped;
|
|
77
86
|
const basePath = targetUrl.split("?")[0] || targetUrl;
|
|
78
|
-
targetUrl =
|
|
87
|
+
targetUrl = queryString ? `${basePath}?${queryString}` : basePath;
|
|
79
88
|
}
|
|
80
89
|
}
|
|
81
|
-
const originalHeaders = getHeaders(event);
|
|
82
90
|
const headers = {};
|
|
83
91
|
for (const [key, value] of Object.entries(originalHeaders)) {
|
|
84
|
-
if (!value)
|
|
92
|
+
if (!value)
|
|
93
|
+
continue;
|
|
85
94
|
const lowerKey = key.toLowerCase();
|
|
86
|
-
if (
|
|
87
|
-
|
|
95
|
+
if (lowerKey === "host")
|
|
96
|
+
continue;
|
|
97
|
+
if (SENSITIVE_HEADERS.includes(lowerKey))
|
|
98
|
+
continue;
|
|
99
|
+
if (lowerKey === "content-length") {
|
|
100
|
+
if (anyPrivacy && !isBinaryBody)
|
|
101
|
+
continue;
|
|
102
|
+
headers[lowerKey] = value;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
88
105
|
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") {
|
|
89
|
-
if (privacy.ip)
|
|
106
|
+
if (privacy.ip)
|
|
107
|
+
continue;
|
|
90
108
|
headers[lowerKey] = value;
|
|
91
109
|
continue;
|
|
92
110
|
}
|
|
@@ -99,11 +117,12 @@ export default defineEventHandler(async (event) => {
|
|
|
99
117
|
continue;
|
|
100
118
|
}
|
|
101
119
|
if (lowerKey === "sec-ch-ua" || lowerKey === "sec-ch-ua-full-version-list") {
|
|
102
|
-
headers[lowerKey] = privacy.hardware ? value.replace(
|
|
120
|
+
headers[lowerKey] = privacy.hardware ? value.replace(CLIENT_HINT_VERSION_RE, ';v="$1"') : value;
|
|
103
121
|
continue;
|
|
104
122
|
}
|
|
105
123
|
if (lowerKey === "sec-ch-ua-platform-version" || lowerKey === "sec-ch-ua-arch" || lowerKey === "sec-ch-ua-model" || lowerKey === "sec-ch-ua-bitness") {
|
|
106
|
-
if (privacy.hardware)
|
|
124
|
+
if (privacy.hardware)
|
|
125
|
+
continue;
|
|
107
126
|
headers[lowerKey] = value;
|
|
108
127
|
continue;
|
|
109
128
|
}
|
|
@@ -123,98 +142,126 @@ export default defineEventHandler(async (event) => {
|
|
|
123
142
|
}
|
|
124
143
|
let body;
|
|
125
144
|
let rawBody;
|
|
126
|
-
|
|
145
|
+
let passthroughBody = false;
|
|
127
146
|
const method = event.method?.toUpperCase();
|
|
128
|
-
const
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
147
|
+
const isWriteMethod = method === "POST" || method === "PUT" || method === "PATCH";
|
|
148
|
+
if (isWriteMethod) {
|
|
149
|
+
if (isBinaryBody || !anyPrivacy) {
|
|
150
|
+
passthroughBody = true;
|
|
151
|
+
} else {
|
|
152
|
+
rawBody = await readBody(event);
|
|
153
|
+
if (rawBody != null) {
|
|
154
|
+
if (Array.isArray(rawBody)) {
|
|
155
|
+
body = rawBody.map(
|
|
156
|
+
(item) => item && typeof item === "object" && !Array.isArray(item) ? stripPayloadFingerprinting(item, privacy) : item
|
|
157
|
+
);
|
|
158
|
+
} else if (typeof rawBody === "object") {
|
|
159
|
+
body = stripPayloadFingerprinting(rawBody, privacy);
|
|
160
|
+
} else if (typeof rawBody === "string") {
|
|
161
|
+
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
162
|
+
const params = new URLSearchParams(rawBody);
|
|
163
|
+
const obj = {};
|
|
164
|
+
for (const [key, value] of params.entries()) {
|
|
165
|
+
if (key in obj) {
|
|
166
|
+
const existing = obj[key];
|
|
167
|
+
obj[key] = Array.isArray(existing) ? [...existing, value] : [existing, value];
|
|
168
|
+
} else {
|
|
169
|
+
obj[key] = value;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const stripped = stripPayloadFingerprinting(obj, privacy);
|
|
173
|
+
const out = new URLSearchParams();
|
|
174
|
+
for (const [k, v] of Object.entries(stripped)) {
|
|
175
|
+
if (v === void 0 || v === null)
|
|
176
|
+
continue;
|
|
177
|
+
if (Array.isArray(v)) {
|
|
178
|
+
for (const item of v)
|
|
179
|
+
out.append(k, typeof item === "string" ? item : JSON.stringify(item));
|
|
180
|
+
} else {
|
|
181
|
+
out.append(k, typeof v === "string" ? v : JSON.stringify(v));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
body = out.toString();
|
|
143
185
|
} else {
|
|
144
|
-
|
|
186
|
+
const maybeJson = contentType.includes("json") || (rawBody.startsWith("{") || rawBody.startsWith("["));
|
|
187
|
+
if (maybeJson) {
|
|
188
|
+
let parsed = null;
|
|
189
|
+
try {
|
|
190
|
+
parsed = JSON.parse(rawBody);
|
|
191
|
+
} catch {
|
|
192
|
+
}
|
|
193
|
+
if (Array.isArray(parsed)) {
|
|
194
|
+
body = parsed.map(
|
|
195
|
+
(item) => item && typeof item === "object" && !Array.isArray(item) ? stripPayloadFingerprinting(item, privacy) : item
|
|
196
|
+
);
|
|
197
|
+
} else if (parsed && typeof parsed === "object") {
|
|
198
|
+
body = stripPayloadFingerprinting(parsed, privacy);
|
|
199
|
+
} else {
|
|
200
|
+
body = rawBody;
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
body = rawBody;
|
|
204
|
+
}
|
|
145
205
|
}
|
|
146
|
-
} else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
147
|
-
const params = new URLSearchParams(rawBody);
|
|
148
|
-
const obj = {};
|
|
149
|
-
params.forEach((value, key) => {
|
|
150
|
-
obj[key] = value;
|
|
151
|
-
});
|
|
152
|
-
const stripped = stripPayloadFingerprinting(obj, privacy);
|
|
153
|
-
const stringified = {};
|
|
154
|
-
for (const [k, v] of Object.entries(stripped)) {
|
|
155
|
-
if (v === void 0 || v === null) continue;
|
|
156
|
-
stringified[k] = typeof v === "string" ? v : JSON.stringify(v);
|
|
157
|
-
}
|
|
158
|
-
body = new URLSearchParams(stringified).toString();
|
|
159
206
|
} else {
|
|
160
207
|
body = rawBody;
|
|
161
208
|
}
|
|
162
|
-
} else {
|
|
163
|
-
body = rawBody;
|
|
164
209
|
}
|
|
165
|
-
} else {
|
|
166
|
-
body = rawBody;
|
|
167
210
|
}
|
|
168
211
|
}
|
|
212
|
+
const nitro = useNitroApp();
|
|
169
213
|
await nitro.hooks.callHook("nuxt-scripts:proxy", {
|
|
170
214
|
timestamp: Date.now(),
|
|
171
215
|
path: event.path,
|
|
172
216
|
targetUrl,
|
|
173
217
|
method: method || "GET",
|
|
174
218
|
privacy,
|
|
219
|
+
passthroughBody,
|
|
175
220
|
original: {
|
|
176
221
|
headers: { ...originalHeaders },
|
|
177
222
|
query: originalQuery,
|
|
178
|
-
body: rawBody ?? null
|
|
223
|
+
body: passthroughBody ? "<passthrough>" : rawBody ?? null
|
|
179
224
|
},
|
|
180
225
|
stripped: {
|
|
181
226
|
headers,
|
|
182
|
-
query:
|
|
183
|
-
body: body ?? null
|
|
227
|
+
query: strippedQueryRecord ?? originalQuery,
|
|
228
|
+
body: passthroughBody ? "<passthrough>" : body ?? null
|
|
184
229
|
}
|
|
185
230
|
});
|
|
186
231
|
log("[proxy] Fetching:", targetUrl);
|
|
187
232
|
const controller = new AbortController();
|
|
188
233
|
const timeoutId = setTimeout(() => controller.abort(), 15e3);
|
|
234
|
+
let fetchBody;
|
|
235
|
+
if (passthroughBody) {
|
|
236
|
+
fetchBody = getRequestWebStream(event);
|
|
237
|
+
} else if (body !== void 0) {
|
|
238
|
+
fetchBody = typeof body === "string" ? body : JSON.stringify(body);
|
|
239
|
+
}
|
|
189
240
|
let response;
|
|
190
241
|
try {
|
|
191
242
|
response = await fetch(targetUrl, {
|
|
192
243
|
method: method || "GET",
|
|
193
244
|
headers,
|
|
194
|
-
body:
|
|
245
|
+
body: fetchBody,
|
|
195
246
|
credentials: "omit",
|
|
196
247
|
// Don't send cookies to third parties
|
|
197
|
-
signal: controller.signal
|
|
248
|
+
signal: controller.signal,
|
|
249
|
+
// @ts-expect-error Node fetch supports duplex for streaming request bodies
|
|
250
|
+
duplex: passthroughBody ? "half" : void 0
|
|
198
251
|
});
|
|
199
252
|
} catch (err) {
|
|
200
|
-
|
|
201
|
-
log("[proxy] Fetch error:", err instanceof Error ? err.message : err);
|
|
202
|
-
if (path.includes("/collect") || path.includes("/tr") || path.includes("/events")) {
|
|
203
|
-
event.node.res.statusCode = 204;
|
|
204
|
-
return "";
|
|
205
|
-
}
|
|
206
|
-
const isTimeout = err instanceof Error && (err.message.includes("aborted") || err.message.includes("timeout"));
|
|
253
|
+
log("[proxy] Upstream error:", err);
|
|
207
254
|
throw createError({
|
|
208
|
-
statusCode:
|
|
209
|
-
statusMessage:
|
|
210
|
-
message:
|
|
255
|
+
statusCode: 502,
|
|
256
|
+
statusMessage: "Bad Gateway",
|
|
257
|
+
message: `Proxy upstream request failed: ${targetUrl}`
|
|
211
258
|
});
|
|
259
|
+
} finally {
|
|
260
|
+
clearTimeout(timeoutId);
|
|
212
261
|
}
|
|
213
|
-
clearTimeout(timeoutId);
|
|
214
262
|
log("[proxy] Response:", response.status, response.statusText);
|
|
215
|
-
const skipHeaders = ["set-cookie", "transfer-encoding", "content-encoding", "content-length"];
|
|
216
263
|
response.headers.forEach((value, key) => {
|
|
217
|
-
if (!
|
|
264
|
+
if (!SKIP_RESPONSE_HEADERS.has(key.toLowerCase())) {
|
|
218
265
|
setResponseHeader(event, key, value);
|
|
219
266
|
}
|
|
220
267
|
});
|
|
@@ -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
|
+
}
|
|
@@ -20,7 +20,7 @@ export interface ProxyPrivacy {
|
|
|
20
20
|
* Privacy input: `true` = full anonymize, `false` = passthrough (still strips sensitive headers),
|
|
21
21
|
* or a `ProxyPrivacy` object for granular control (unset flags default to `false` — opt-in).
|
|
22
22
|
*/
|
|
23
|
-
export type ProxyPrivacyInput = boolean | ProxyPrivacy
|
|
23
|
+
export type ProxyPrivacyInput = boolean | ProxyPrivacy;
|
|
24
24
|
/** Resolved privacy with all flags explicitly set. */
|
|
25
25
|
export type ResolvedProxyPrivacy = Required<ProxyPrivacy>;
|
|
26
26
|
/**
|
|
@@ -71,7 +71,6 @@ export declare const STRIP_PARAMS: {
|
|
|
71
71
|
browserVersion: string[];
|
|
72
72
|
browserData: string[];
|
|
73
73
|
location: string[];
|
|
74
|
-
canvas: string[];
|
|
75
74
|
deviceInfo: string[];
|
|
76
75
|
};
|
|
77
76
|
/**
|
|
@@ -130,12 +129,4 @@ export declare function generalizeTimezone(value: unknown): string | number;
|
|
|
130
129
|
* (timezone, language, screen dimensions) while keeping low-entropy values.
|
|
131
130
|
*/
|
|
132
131
|
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
132
|
export declare function stripPayloadFingerprinting(payload: Record<string, unknown>, privacy?: ResolvedProxyPrivacy): Record<string, unknown>;
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
const FULL_PRIVACY = { ip: true, userAgent: true, language: true, screen: true, timezone: true, hardware: true };
|
|
2
2
|
const NO_PRIVACY = { ip: false, userAgent: false, language: false, screen: false, timezone: false, hardware: false };
|
|
3
|
+
const MAJOR_VERSION_RE = /^(\d+)/;
|
|
4
|
+
const VERSION_RE = /^(\d+)(([.\-_])\d+)*/;
|
|
5
|
+
const VERSION_SPLIT_RE = /[.\-_]/;
|
|
6
|
+
const SNAPCHAT_VERSION_RE = /("version"\s*:\s*")(\d+(?:\.\d+)*)/g;
|
|
7
|
+
const GA_VERSION_RE = /;(\d+(?:\.\d+)*)/g;
|
|
8
|
+
const UPPERCASE_RE = /^[A-Z]/;
|
|
9
|
+
const LANG_CODE_RE = /^[a-z]{2}(?:-[a-z]{2,})?$/i;
|
|
3
10
|
export function resolvePrivacy(input) {
|
|
4
|
-
if (input === true)
|
|
5
|
-
|
|
11
|
+
if (input === true)
|
|
12
|
+
return { ...FULL_PRIVACY };
|
|
13
|
+
if (input === false || input === void 0)
|
|
14
|
+
return { ...NO_PRIVACY };
|
|
6
15
|
return {
|
|
7
16
|
ip: input.ip ?? false,
|
|
8
17
|
userAgent: input.userAgent ?? false,
|
|
@@ -13,8 +22,10 @@ export function resolvePrivacy(input) {
|
|
|
13
22
|
};
|
|
14
23
|
}
|
|
15
24
|
export function mergePrivacy(base, override) {
|
|
16
|
-
if (override === void 0
|
|
17
|
-
|
|
25
|
+
if (override === void 0)
|
|
26
|
+
return base;
|
|
27
|
+
if (typeof override === "boolean")
|
|
28
|
+
return resolvePrivacy(override);
|
|
18
29
|
return {
|
|
19
30
|
ip: override.ip !== void 0 ? override.ip : base.ip,
|
|
20
31
|
userAgent: override.userAgent !== void 0 ? override.userAgent : base.userAgent,
|
|
@@ -67,11 +78,13 @@ export const STRIP_PARAMS = {
|
|
|
67
78
|
// Browser version lists — generalized to major versions (d_bvs = Snapchat, uafvl = GA Client Hints)
|
|
68
79
|
browserVersion: ["d_bvs", "uafvl"],
|
|
69
80
|
// Browser data lists — replaced with empty value
|
|
70
|
-
browserData: ["plugins", "fonts"],
|
|
81
|
+
browserData: ["plugins", "fonts", "audiofingerprint"],
|
|
71
82
|
// Location/Timezone — generalized
|
|
72
83
|
location: ["tz", "timezone", "timezoneoffset"],
|
|
73
|
-
// Canvas/WebGL
|
|
74
|
-
|
|
84
|
+
// Canvas/WebGL fingerprints — neutralized at build time via AST rewriting (rewrite-ast.ts).
|
|
85
|
+
// These params are no longer stripped at runtime; the source APIs (toDataURL, WEBGL_debug_renderer_info)
|
|
86
|
+
// are neutralized before the script ever runs.
|
|
87
|
+
// canvas: ['canvas', 'webgl'],
|
|
75
88
|
// Combined device fingerprinting (X/Twitter dv param contains: timezone, locale, vendor, platform, screen, etc.)
|
|
76
89
|
deviceInfo: ["dv", "device_info", "deviceinfo"]
|
|
77
90
|
};
|
|
@@ -81,7 +94,7 @@ export const NORMALIZE_PARAMS = {
|
|
|
81
94
|
};
|
|
82
95
|
export function anonymizeIP(ip) {
|
|
83
96
|
if (ip.includes(":")) {
|
|
84
|
-
return ip.split(":").slice(0, 3).join(":")
|
|
97
|
+
return `${ip.split(":").slice(0, 3).join(":")}::`;
|
|
85
98
|
}
|
|
86
99
|
const parts = ip.split(".");
|
|
87
100
|
if (parts.length === 4) {
|
|
@@ -103,7 +116,7 @@ export function normalizeUserAgent(ua) {
|
|
|
103
116
|
const idx = ua.indexOf(pattern);
|
|
104
117
|
if (idx !== -1) {
|
|
105
118
|
const versionStart = idx + pattern.length;
|
|
106
|
-
const majorVersion = ua.slice(versionStart).match(
|
|
119
|
+
const majorVersion = ua.slice(versionStart).match(MAJOR_VERSION_RE)?.[1];
|
|
107
120
|
if (majorVersion)
|
|
108
121
|
return `Mozilla/5.0 (compatible; ${family}/${majorVersion}.0)`;
|
|
109
122
|
}
|
|
@@ -119,8 +132,10 @@ const SCREEN_BUCKETS = {
|
|
|
119
132
|
mobile: { w: 360, h: 640 }
|
|
120
133
|
};
|
|
121
134
|
function getDeviceClass(width) {
|
|
122
|
-
if (width >= 1200)
|
|
123
|
-
|
|
135
|
+
if (width >= 1200)
|
|
136
|
+
return "desktop";
|
|
137
|
+
if (width >= 700)
|
|
138
|
+
return "tablet";
|
|
124
139
|
return "mobile";
|
|
125
140
|
}
|
|
126
141
|
export function generalizeScreen(value, dimension) {
|
|
@@ -139,31 +154,38 @@ export function generalizeScreen(value, dimension) {
|
|
|
139
154
|
}
|
|
140
155
|
export function generalizeHardware(value) {
|
|
141
156
|
const num = typeof value === "number" ? value : Number(value);
|
|
142
|
-
if (Number.isNaN(num))
|
|
143
|
-
|
|
144
|
-
if (num >=
|
|
145
|
-
|
|
157
|
+
if (Number.isNaN(num))
|
|
158
|
+
return 4;
|
|
159
|
+
if (num >= 16)
|
|
160
|
+
return 16;
|
|
161
|
+
if (num >= 8)
|
|
162
|
+
return 8;
|
|
163
|
+
if (num >= 4)
|
|
164
|
+
return 4;
|
|
146
165
|
return 2;
|
|
147
166
|
}
|
|
148
167
|
export function generalizeVersion(value) {
|
|
149
|
-
if (typeof value !== "string")
|
|
150
|
-
|
|
151
|
-
|
|
168
|
+
if (typeof value !== "string")
|
|
169
|
+
return String(value);
|
|
170
|
+
const match = value.match(VERSION_RE);
|
|
171
|
+
if (!match)
|
|
172
|
+
return String(value);
|
|
152
173
|
const major = match[1];
|
|
153
174
|
const sep = match[3] || ".";
|
|
154
|
-
const segmentCount = value.split(
|
|
155
|
-
return major +
|
|
175
|
+
const segmentCount = value.split(VERSION_SPLIT_RE).length;
|
|
176
|
+
return major + `${sep}0`.repeat(segmentCount - 1);
|
|
156
177
|
}
|
|
157
178
|
export function generalizeBrowserVersions(value) {
|
|
158
|
-
if (typeof value !== "string")
|
|
179
|
+
if (typeof value !== "string")
|
|
180
|
+
return String(value);
|
|
159
181
|
const zeroSegments = (ver) => {
|
|
160
182
|
const parts = ver.split(".");
|
|
161
183
|
return parts[0] + parts.slice(1).map(() => ".0").join("");
|
|
162
184
|
};
|
|
163
185
|
if (value.includes('"version"'))
|
|
164
|
-
return value.replace(
|
|
186
|
+
return value.replace(SNAPCHAT_VERSION_RE, (_, prefix, ver) => prefix + zeroSegments(ver));
|
|
165
187
|
if (value.includes(";"))
|
|
166
|
-
return value.replace(
|
|
188
|
+
return value.replace(GA_VERSION_RE, (_, ver) => `;${zeroSegments(ver)}`);
|
|
167
189
|
return value;
|
|
168
190
|
}
|
|
169
191
|
export function generalizeTimezone(value) {
|
|
@@ -178,15 +200,16 @@ export function generalizeTimezone(value) {
|
|
|
178
200
|
export function anonymizeDeviceInfo(value) {
|
|
179
201
|
const sep = value.includes("|") ? "|" : "&";
|
|
180
202
|
const parts = value.split(sep);
|
|
181
|
-
if (parts.length < 4)
|
|
203
|
+
if (parts.length < 4)
|
|
204
|
+
return value;
|
|
182
205
|
const result = [...parts];
|
|
183
206
|
for (let i = 0; i < parts.length; i++) {
|
|
184
207
|
const part = parts[i];
|
|
185
|
-
if (part.includes("/") &&
|
|
208
|
+
if (part.includes("/") && UPPERCASE_RE.test(part)) {
|
|
186
209
|
result[i] = String(generalizeTimezone(part));
|
|
187
210
|
continue;
|
|
188
211
|
}
|
|
189
|
-
if (
|
|
212
|
+
if (LANG_CODE_RE.test(part)) {
|
|
190
213
|
result[i] = normalizeLanguage(part);
|
|
191
214
|
continue;
|
|
192
215
|
}
|
|
@@ -209,6 +232,13 @@ export function anonymizeDeviceInfo(value) {
|
|
|
209
232
|
}
|
|
210
233
|
return result.join(sep);
|
|
211
234
|
}
|
|
235
|
+
function matchesParam(key, params) {
|
|
236
|
+
const lk = key.toLowerCase();
|
|
237
|
+
return params.some((pm) => {
|
|
238
|
+
const lp = pm.toLowerCase();
|
|
239
|
+
return lk === lp || lk.startsWith(`${lp}[`);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
212
242
|
export function stripPayloadFingerprinting(payload, privacy) {
|
|
213
243
|
const p = privacy || FULL_PRIVACY;
|
|
214
244
|
const result = {};
|
|
@@ -216,18 +246,12 @@ export function stripPayloadFingerprinting(payload, privacy) {
|
|
|
216
246
|
for (const [key, value] of Object.entries(payload)) {
|
|
217
247
|
if (key.toLowerCase() === "sw") {
|
|
218
248
|
const num = typeof value === "number" ? value : Number(value);
|
|
219
|
-
if (!Number.isNaN(num))
|
|
249
|
+
if (!Number.isNaN(num))
|
|
250
|
+
deviceClass = getDeviceClass(num);
|
|
220
251
|
}
|
|
221
252
|
}
|
|
222
253
|
for (const [key, value] of Object.entries(payload)) {
|
|
223
254
|
const lowerKey = key.toLowerCase();
|
|
224
|
-
const matchesParam = (key2, params) => {
|
|
225
|
-
const lk = key2.toLowerCase();
|
|
226
|
-
return params.some((pm) => {
|
|
227
|
-
const lp = pm.toLowerCase();
|
|
228
|
-
return lk === lp || lk.startsWith(lp + "[");
|
|
229
|
-
});
|
|
230
|
-
};
|
|
231
255
|
const isLanguageParam = NORMALIZE_PARAMS.language.some((pm) => lowerKey === pm.toLowerCase());
|
|
232
256
|
if (isLanguageParam) {
|
|
233
257
|
if (Array.isArray(value)) {
|
|
@@ -264,7 +288,7 @@ export function stripPayloadFingerprinting(payload, privacy) {
|
|
|
264
288
|
continue;
|
|
265
289
|
}
|
|
266
290
|
if (matchesParam(key, STRIP_PARAMS.hardware)) {
|
|
267
|
-
result[key] = p.
|
|
291
|
+
result[key] = p.hardware ? generalizeHardware(value) : value;
|
|
268
292
|
continue;
|
|
269
293
|
}
|
|
270
294
|
if (matchesParam(key, STRIP_PARAMS.version)) {
|
|
@@ -280,11 +304,7 @@ export function stripPayloadFingerprinting(payload, privacy) {
|
|
|
280
304
|
continue;
|
|
281
305
|
}
|
|
282
306
|
if (matchesParam(key, STRIP_PARAMS.browserData)) {
|
|
283
|
-
result[key] = p.hardware ? Array.isArray(value) ? [] : "" : value;
|
|
284
|
-
continue;
|
|
285
|
-
}
|
|
286
|
-
if (matchesParam(key, STRIP_PARAMS.canvas)) {
|
|
287
|
-
result[key] = p.hardware ? typeof value === "number" ? 0 : typeof value === "object" ? {} : "" : value;
|
|
307
|
+
result[key] = p.hardware ? Array.isArray(value) ? [] : typeof value === "number" ? 0 : "" : value;
|
|
288
308
|
continue;
|
|
289
309
|
}
|
|
290
310
|
if (matchesParam(key, STRIP_PARAMS.deviceInfo)) {
|