@nuxt/scripts 1.0.0-beta.2 → 1.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/200.html +1 -1
- package/dist/client/404.html +1 -1
- package/dist/client/_nuxt/{B66N9HCo.js → CD5B-xvT.js} +1 -1
- package/dist/client/_nuxt/D-kOnTuH.js +162 -0
- package/dist/client/_nuxt/{DvH517bE.js → DdVDSbUA.js} +1 -1
- package/dist/client/_nuxt/{DfLgoB--.js → Ds2G8aQM.js} +1 -1
- package/dist/client/_nuxt/builds/latest.json +1 -1
- package/dist/client/_nuxt/builds/meta/cc1ba70f-082b-4dc7-8639-5f076fef2cfa.json +1 -0
- package/dist/client/index.html +1 -1
- package/dist/module.d.mts +20 -13
- package/dist/module.json +1 -1
- package/dist/module.mjs +51 -3
- package/dist/registry.mjs +1 -0
- package/dist/runtime/registry/posthog.d.ts +1 -0
- package/dist/runtime/registry/posthog.js +2 -8
- package/dist/runtime/server/proxy-handler.js +59 -43
- package/dist/runtime/server/utils/privacy.d.ts +45 -1
- package/dist/runtime/server/utils/privacy.js +60 -19
- package/dist/runtime/utils/pure.js +1 -1
- package/package.json +6 -6
- package/dist/client/_nuxt/B8XOar-X.js +0 -162
- package/dist/client/_nuxt/builds/meta/133a46c5-a5c1-4a63-87d1-037947a5bcdb.json +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{_ as o,c as s,o as a,a as t,t as r}from"./
|
|
1
|
+
import{_ as o,c as s,o as a,a as t,t as r}from"./D-kOnTuH.js";import{u as i}from"./Ds2G8aQM.js";const u={class:"antialiased bg-white dark:bg-[#020420] dark:text-white font-sans grid min-h-screen overflow-hidden place-content-center text-[#020420] tracking-wide"},l={class:"max-w-520px text-center"},c=["textContent"],d=["textContent"],p=["textContent"],f={__name:"error-500",props:{appName:{type:String,default:"Nuxt"},status:{type:Number,default:500},statusText:{type:String,default:"Internal server error"},description:{type:String,default:"This page is temporarily unavailable."},refresh:{type:String,default:"Refresh this page"}},setup(e){const n=e;return i({title:`${n.status} - ${n.statusText} | ${n.appName}`,script:[{innerHTML:`!function(){const e=document.createElement("link").relList;if(!(e&&e.supports&&e.supports("modulepreload"))){for(const e of document.querySelectorAll('link[rel="modulepreload"]'))r(e);new MutationObserver(e=>{for(const o of e)if("childList"===o.type)for(const e of o.addedNodes)"LINK"===e.tagName&&"modulepreload"===e.rel&&r(e)}).observe(document,{childList:!0,subtree:!0})}function r(e){if(e.ep)return;e.ep=!0;const r=function(e){const r={};return e.integrity&&(r.integrity=e.integrity),e.referrerPolicy&&(r.referrerPolicy=e.referrerPolicy),"use-credentials"===e.crossOrigin?r.credentials="include":"anonymous"===e.crossOrigin?r.credentials="omit":r.credentials="same-origin",r}(e);fetch(e.href,r)}}();`}],style:[{innerHTML:'*,:after,:before{border-color:var(--un-default-border-color,#e5e7eb);border-style:solid;border-width:0;box-sizing:border-box}:after,:before{--un-content:""}html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}h1,h2{font-size:inherit;font-weight:inherit}h1,h2,p{margin:0}*,:after,:before{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 transparent;--un-ring-shadow:0 0 transparent;--un-shadow-inset: ;--un-shadow:0 0 transparent;--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgba(147,197,253,.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: }'}]}),(h,m)=>(a(),s("div",u,[t("div",l,[t("h1",{class:"font-semibold leading-none mb-4 sm:text-[110px] tabular-nums text-[80px]",textContent:r(e.status)},null,8,c),t("h2",{class:"font-semibold mb-2 sm:text-3xl text-2xl",textContent:r(e.statusText)},null,8,d),t("p",{class:"mb-4 px-2 text-[#64748B] text-md",textContent:r(e.description)},null,8,p)])]))}},x=o(f,[["__scopeId","data-v-2367f596"]]);export{x as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{u as a,f as s,h as u,i as r,g as h}from"./
|
|
1
|
+
import{u as a,f as s,h as u,i as r,g as h}from"./D-kOnTuH.js";function i(t){const e=t||s();return e.ssrContext?.head||e.runWithContext(()=>{if(u()){const n=r(h);if(!n)throw new Error("[nuxt] [unhead] Missing Unhead instance.");return n}})}function d(t,e={}){const n=e.head||i(e.nuxt);return a(t,{head:n,...e})}export{d as u};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"id":"
|
|
1
|
+
{"id":"cc1ba70f-082b-4dc7-8639-5f076fef2cfa","timestamp":1772035753039}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"cc1ba70f-082b-4dc7-8639-5f076fef2cfa","timestamp":1772035753039,"prerendered":[]}
|
package/dist/client/index.html
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/__nuxt-scripts/_nuxt/entry.D45OuV0w.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-scripts/_nuxt/
|
|
1
|
+
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/__nuxt-scripts/_nuxt/entry.D45OuV0w.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-scripts/_nuxt/D-kOnTuH.js"><script type="module" src="/__nuxt-scripts/_nuxt/D-kOnTuH.js" crossorigin></script></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{"nuxt-scripts":{version:"",defaultScriptOptions:{trigger:"onNuxtReady"},googleStaticMapsProxy:""},"nuxt-scripts-sw":{path:"/_nuxt-scripts-sw.js"}},app:{baseURL:"/__nuxt-scripts",buildId:"cc1ba70f-082b-4dc7-8639-5f076fef2cfa",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1772035757664,false]</script></body></html>
|
package/dist/module.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
2
|
import { FetchOptions } from 'ofetch';
|
|
3
3
|
import { RegistryScripts, NuxtConfigScriptRegistry, NuxtUseScriptOptionsSerializable, NuxtUseScriptInput } from '../dist/runtime/types.js';
|
|
4
|
+
import { ProxyPrivacyInput } from '../dist/runtime/server/utils/privacy.js';
|
|
4
5
|
|
|
5
6
|
declare module '@nuxt/schema' {
|
|
6
7
|
interface NuxtHooks {
|
|
@@ -8,17 +9,21 @@ declare module '@nuxt/schema' {
|
|
|
8
9
|
}
|
|
9
10
|
}
|
|
10
11
|
/**
|
|
11
|
-
*
|
|
12
|
+
* Global privacy override for all first-party proxy requests.
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
14
|
+
* By default (`undefined`), each script uses its own privacy controls declared in the registry.
|
|
15
|
+
* Setting this overrides all per-script defaults:
|
|
15
16
|
*
|
|
16
|
-
* - `
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
17
|
+
* - `true` - Full anonymize: anonymizes IP, normalizes User-Agent/language,
|
|
18
|
+
* generalizes screen/hardware/canvas/timezone data.
|
|
19
|
+
*
|
|
20
|
+
* - `false` - Passthrough: forwards headers and data, but strips sensitive
|
|
21
|
+
* auth/session headers (cookie, authorization).
|
|
22
|
+
*
|
|
23
|
+
* - `{ ip: false }` - Selective: override individual flags. Unset flags inherit
|
|
24
|
+
* from the per-script default.
|
|
20
25
|
*/
|
|
21
|
-
type FirstPartyPrivacy =
|
|
26
|
+
type FirstPartyPrivacy = ProxyPrivacyInput;
|
|
22
27
|
interface FirstPartyOptions {
|
|
23
28
|
/**
|
|
24
29
|
* Path prefix for serving bundled scripts.
|
|
@@ -38,14 +43,16 @@ interface FirstPartyOptions {
|
|
|
38
43
|
*/
|
|
39
44
|
collectPrefix?: string;
|
|
40
45
|
/**
|
|
41
|
-
*
|
|
46
|
+
* Global privacy override for all proxied scripts.
|
|
42
47
|
*
|
|
43
|
-
*
|
|
48
|
+
* By default, each script uses its own privacy controls from the registry.
|
|
49
|
+
* Set this to override all scripts at once:
|
|
44
50
|
*
|
|
45
|
-
* - `
|
|
46
|
-
* - `
|
|
51
|
+
* - `true` - Full anonymize for all scripts
|
|
52
|
+
* - `false` - Passthrough for all scripts (still strips sensitive auth headers)
|
|
53
|
+
* - `{ ip: false }` - Selective override (unset flags inherit per-script defaults)
|
|
47
54
|
*
|
|
48
|
-
* @default
|
|
55
|
+
* @default undefined
|
|
49
56
|
*/
|
|
50
57
|
privacy?: FirstPartyPrivacy;
|
|
51
58
|
}
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -126,6 +126,8 @@ function setupPublicAssetStrategy(options = {}) {
|
|
|
126
126
|
function buildProxyConfig(collectPrefix) {
|
|
127
127
|
return {
|
|
128
128
|
googleAnalytics: {
|
|
129
|
+
// GA4: screen/timezone/UA needed for device, time, and OS reports; rest anonymized safely
|
|
130
|
+
privacy: { ip: true, userAgent: false, language: true, screen: false, timezone: false, hardware: true },
|
|
129
131
|
rewrite: [
|
|
130
132
|
// Modern gtag.js uses www.google.com/g/collect
|
|
131
133
|
{ from: "www.google.com/g/collect", to: `${collectPrefix}/ga/g/collect` },
|
|
@@ -156,6 +158,8 @@ function buildProxyConfig(collectPrefix) {
|
|
|
156
158
|
}
|
|
157
159
|
},
|
|
158
160
|
googleTagManager: {
|
|
161
|
+
// GTM: container only, passes data through — downstream tags have their own privacy
|
|
162
|
+
privacy: { ip: false, userAgent: false, language: false, screen: false, timezone: false, hardware: false },
|
|
159
163
|
rewrite: [
|
|
160
164
|
{ from: "www.googletagmanager.com", to: `${collectPrefix}/gtm` }
|
|
161
165
|
],
|
|
@@ -164,6 +168,8 @@ function buildProxyConfig(collectPrefix) {
|
|
|
164
168
|
}
|
|
165
169
|
},
|
|
166
170
|
metaPixel: {
|
|
171
|
+
// Meta: untrusted ad network — full anonymization
|
|
172
|
+
privacy: { ip: true, userAgent: true, language: true, screen: true, timezone: true, hardware: true },
|
|
167
173
|
rewrite: [
|
|
168
174
|
// SDK script loading
|
|
169
175
|
{ from: "connect.facebook.net", to: `${collectPrefix}/meta` },
|
|
@@ -182,6 +188,8 @@ function buildProxyConfig(collectPrefix) {
|
|
|
182
188
|
}
|
|
183
189
|
},
|
|
184
190
|
tiktokPixel: {
|
|
191
|
+
// TikTok: untrusted ad network — full anonymization
|
|
192
|
+
privacy: { ip: true, userAgent: true, language: true, screen: true, timezone: true, hardware: true },
|
|
185
193
|
rewrite: [
|
|
186
194
|
{ from: "analytics.tiktok.com", to: `${collectPrefix}/tiktok` }
|
|
187
195
|
],
|
|
@@ -190,6 +198,8 @@ function buildProxyConfig(collectPrefix) {
|
|
|
190
198
|
}
|
|
191
199
|
},
|
|
192
200
|
segment: {
|
|
201
|
+
// Segment: trusted data pipeline — needs maximum fidelity for downstream destinations
|
|
202
|
+
privacy: { ip: false, userAgent: false, language: false, screen: false, timezone: false, hardware: false },
|
|
193
203
|
rewrite: [
|
|
194
204
|
{ from: "api.segment.io", to: `${collectPrefix}/segment` },
|
|
195
205
|
{ from: "cdn.segment.com", to: `${collectPrefix}/segment-cdn` }
|
|
@@ -200,6 +210,8 @@ function buildProxyConfig(collectPrefix) {
|
|
|
200
210
|
}
|
|
201
211
|
},
|
|
202
212
|
xPixel: {
|
|
213
|
+
// X/Twitter: untrusted ad network — full anonymization
|
|
214
|
+
privacy: { ip: true, userAgent: true, language: true, screen: true, timezone: true, hardware: true },
|
|
203
215
|
rewrite: [
|
|
204
216
|
{ from: "analytics.twitter.com", to: `${collectPrefix}/x` },
|
|
205
217
|
{ from: "t.co", to: `${collectPrefix}/x-t` }
|
|
@@ -210,6 +222,8 @@ function buildProxyConfig(collectPrefix) {
|
|
|
210
222
|
}
|
|
211
223
|
},
|
|
212
224
|
snapchatPixel: {
|
|
225
|
+
// Snapchat: untrusted ad network — full anonymization
|
|
226
|
+
privacy: { ip: true, userAgent: true, language: true, screen: true, timezone: true, hardware: true },
|
|
213
227
|
rewrite: [
|
|
214
228
|
{ from: "tr.snapchat.com", to: `${collectPrefix}/snap` }
|
|
215
229
|
],
|
|
@@ -218,6 +232,8 @@ function buildProxyConfig(collectPrefix) {
|
|
|
218
232
|
}
|
|
219
233
|
},
|
|
220
234
|
redditPixel: {
|
|
235
|
+
// Reddit: untrusted ad network — full anonymization
|
|
236
|
+
privacy: { ip: true, userAgent: true, language: true, screen: true, timezone: true, hardware: true },
|
|
221
237
|
rewrite: [
|
|
222
238
|
{ from: "alb.reddit.com", to: `${collectPrefix}/reddit` }
|
|
223
239
|
],
|
|
@@ -226,6 +242,8 @@ function buildProxyConfig(collectPrefix) {
|
|
|
226
242
|
}
|
|
227
243
|
},
|
|
228
244
|
clarity: {
|
|
245
|
+
// Clarity: screen/UA/timezone needed for heatmaps and device filtering; rest anonymized
|
|
246
|
+
privacy: { ip: true, userAgent: false, language: true, screen: false, timezone: false, hardware: true },
|
|
229
247
|
rewrite: [
|
|
230
248
|
// Main clarity domain
|
|
231
249
|
{ from: "www.clarity.ms", to: `${collectPrefix}/clarity` },
|
|
@@ -243,7 +261,22 @@ function buildProxyConfig(collectPrefix) {
|
|
|
243
261
|
[`${collectPrefix}/clarity-events/**`]: { proxy: "https://e.clarity.ms/**" }
|
|
244
262
|
}
|
|
245
263
|
},
|
|
264
|
+
posthog: {
|
|
265
|
+
// No rewrites needed - PostHog uses NPM mode, SDK URLs are set via api_host config
|
|
266
|
+
// PostHog: needs real IP for GeoIP enrichment + feature flag targeting
|
|
267
|
+
privacy: { ip: false, userAgent: false, language: false, screen: false, timezone: false, hardware: false },
|
|
268
|
+
routes: {
|
|
269
|
+
// US region
|
|
270
|
+
[`${collectPrefix}/ph/static/**`]: { proxy: "https://us-assets.i.posthog.com/static/**" },
|
|
271
|
+
[`${collectPrefix}/ph/**`]: { proxy: "https://us.i.posthog.com/**" },
|
|
272
|
+
// EU region
|
|
273
|
+
[`${collectPrefix}/ph-eu/static/**`]: { proxy: "https://eu-assets.i.posthog.com/static/**" },
|
|
274
|
+
[`${collectPrefix}/ph-eu/**`]: { proxy: "https://eu.i.posthog.com/**" }
|
|
275
|
+
}
|
|
276
|
+
},
|
|
246
277
|
hotjar: {
|
|
278
|
+
// Hotjar: screen/UA/timezone needed for heatmaps and device segmentation; rest anonymized
|
|
279
|
+
privacy: { ip: true, userAgent: false, language: true, screen: false, timezone: false, hardware: true },
|
|
247
280
|
rewrite: [
|
|
248
281
|
// Static assets
|
|
249
282
|
{ from: "static.hotjar.com", to: `${collectPrefix}/hotjar` },
|
|
@@ -652,7 +685,7 @@ function NuxtScriptBundleTransformer(options = {
|
|
|
652
685
|
s.appendRight(node.arguments[0].start + 1, ` scriptInput: { src: '${url}'${integrityProps} }, `);
|
|
653
686
|
}
|
|
654
687
|
} else {
|
|
655
|
-
s.
|
|
688
|
+
s.overwrite(node.callee.end, node.end, `({ scriptInput: { src: '${url}'${integrityProps} } })`);
|
|
656
689
|
}
|
|
657
690
|
}
|
|
658
691
|
});
|
|
@@ -1037,7 +1070,7 @@ const module$1 = defineNuxtModule({
|
|
|
1037
1070
|
const firstPartyEnabled = !!config.firstParty;
|
|
1038
1071
|
const firstPartyPrefix = typeof config.firstParty === "object" ? config.firstParty.prefix : void 0;
|
|
1039
1072
|
const firstPartyCollectPrefix = typeof config.firstParty === "object" ? config.firstParty.collectPrefix || "/_proxy" : "/_proxy";
|
|
1040
|
-
const firstPartyPrivacy = typeof config.firstParty === "object" ? config.firstParty.privacy
|
|
1073
|
+
const firstPartyPrivacy = typeof config.firstParty === "object" ? config.firstParty.privacy : void 0;
|
|
1041
1074
|
const assetsPrefix = firstPartyPrefix || config.assets?.prefix || "/_scripts";
|
|
1042
1075
|
if (config.partytown?.length) {
|
|
1043
1076
|
config.registry = config.registry || {};
|
|
@@ -1225,6 +1258,7 @@ export default defineNuxtPlugin({
|
|
|
1225
1258
|
const proxyConfigs = getAllProxyConfigs(firstPartyCollectPrefix);
|
|
1226
1259
|
const registryKeys = Object.keys(config.registry || {});
|
|
1227
1260
|
const neededRoutes = {};
|
|
1261
|
+
const routePrivacyOverrides = {};
|
|
1228
1262
|
const unsupportedScripts = [];
|
|
1229
1263
|
for (const key of registryKeys) {
|
|
1230
1264
|
const script = registryScriptsWithImport.find((s) => s.import.name.toLowerCase() === `usescript${key.toLowerCase()}`);
|
|
@@ -1233,11 +1267,21 @@ export default defineNuxtPlugin({
|
|
|
1233
1267
|
const proxyConfig = proxyConfigs[proxyKey];
|
|
1234
1268
|
if (proxyConfig?.routes) {
|
|
1235
1269
|
Object.assign(neededRoutes, proxyConfig.routes);
|
|
1270
|
+
for (const routePath of Object.keys(proxyConfig.routes)) {
|
|
1271
|
+
routePrivacyOverrides[routePath] = proxyConfig.privacy;
|
|
1272
|
+
}
|
|
1236
1273
|
} else {
|
|
1237
1274
|
unsupportedScripts.push(key);
|
|
1238
1275
|
}
|
|
1239
1276
|
}
|
|
1240
1277
|
}
|
|
1278
|
+
if (config.registry?.posthog && typeof config.registry.posthog === "object") {
|
|
1279
|
+
const phConfig = config.registry.posthog;
|
|
1280
|
+
if (!phConfig.apiHost) {
|
|
1281
|
+
const region = phConfig.region || "us";
|
|
1282
|
+
phConfig.apiHost = region === "eu" ? `${firstPartyCollectPrefix}/ph-eu` : `${firstPartyCollectPrefix}/ph`;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1241
1285
|
if (unsupportedScripts.length && nuxt.options.dev) {
|
|
1242
1286
|
logger.warn(
|
|
1243
1287
|
`First-party mode is enabled but these scripts don't support it yet: ${unsupportedScripts.join(", ")}.
|
|
@@ -1262,13 +1306,17 @@ They will load directly from third-party servers. Request support at https://git
|
|
|
1262
1306
|
nuxt.options.runtimeConfig["nuxt-scripts-proxy"] = {
|
|
1263
1307
|
routes: flatRoutes,
|
|
1264
1308
|
privacy: firstPartyPrivacy,
|
|
1309
|
+
// undefined = use per-script defaults, set = global override
|
|
1310
|
+
routePrivacy: routePrivacyOverrides,
|
|
1311
|
+
// per-script privacy from registry
|
|
1265
1312
|
rewrites: allRewrites
|
|
1266
1313
|
};
|
|
1267
1314
|
if (Object.keys(neededRoutes).length) {
|
|
1268
1315
|
if (nuxt.options.dev) {
|
|
1269
1316
|
const routeCount = Object.keys(neededRoutes).length;
|
|
1270
1317
|
const scriptsCount = registryKeys.length;
|
|
1271
|
-
|
|
1318
|
+
const privacyLabel = firstPartyPrivacy === void 0 ? "per-script" : typeof firstPartyPrivacy === "boolean" ? firstPartyPrivacy ? "anonymize" : "passthrough" : "custom";
|
|
1319
|
+
logger.success(`First-party mode enabled for ${scriptsCount} script(s), ${routeCount} proxy route(s) configured (privacy: ${privacyLabel})`);
|
|
1272
1320
|
if (logger.level >= 4) {
|
|
1273
1321
|
for (const [path, config2] of Object.entries(neededRoutes)) {
|
|
1274
1322
|
logger.debug(` ${path} \u2192 ${config2.proxy}`);
|
package/dist/registry.mjs
CHANGED
|
@@ -29,6 +29,7 @@ async function registry(resolve) {
|
|
|
29
29
|
{
|
|
30
30
|
label: "PostHog",
|
|
31
31
|
src: false,
|
|
32
|
+
proxy: "posthog",
|
|
32
33
|
scriptBundling: false,
|
|
33
34
|
category: "analytics",
|
|
34
35
|
logo: `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 128 128"><path fill="#1d4aff" d="M0 .52v32.15l31.79 31.78V32.3L0 .52zm32.3 32.15v32.15l31.78 31.78V64.45L32.3 32.67zM0 64.97v32.15l31.79 31.78V96.75L0 64.97zm64.6-32.3v32.15l31.78 31.78V64.45L64.6 32.67zm31.78 31.78v32.15l31.78 31.78V96.23l-31.78-31.78zm-64.08.52v32.15l31.78 31.78V96.75L32.3 64.97zM64.6 .52v32.15l31.78 31.78V32.3L64.6 .52zm0 64.45v32.15l31.78 31.78V96.75L64.6 64.97z"/></svg>`,
|
|
@@ -3,6 +3,7 @@ import type { RegistryScriptInput } from '#nuxt-scripts/types';
|
|
|
3
3
|
export declare const PostHogOptions: import("valibot").ObjectSchema<{
|
|
4
4
|
readonly apiKey: import("valibot").StringSchema<undefined>;
|
|
5
5
|
readonly region: import("valibot").OptionalSchema<import("valibot").UnionSchema<[import("valibot").LiteralSchema<"us", undefined>, import("valibot").LiteralSchema<"eu", undefined>], undefined>, undefined>;
|
|
6
|
+
readonly apiHost: import("valibot").OptionalSchema<import("valibot").StringSchema<undefined>, undefined>;
|
|
6
7
|
readonly autocapture: import("valibot").OptionalSchema<import("valibot").BooleanSchema<undefined>, undefined>;
|
|
7
8
|
readonly capturePageview: import("valibot").OptionalSchema<import("valibot").BooleanSchema<undefined>, undefined>;
|
|
8
9
|
readonly capturePageleave: import("valibot").OptionalSchema<import("valibot").BooleanSchema<undefined>, undefined>;
|
|
@@ -4,6 +4,7 @@ import { logger } from "../logger.js";
|
|
|
4
4
|
export const PostHogOptions = object({
|
|
5
5
|
apiKey: string(),
|
|
6
6
|
region: optional(union([literal("us"), literal("eu")])),
|
|
7
|
+
apiHost: optional(string()),
|
|
7
8
|
autocapture: optional(boolean()),
|
|
8
9
|
capturePageview: optional(boolean()),
|
|
9
10
|
capturePageleave: optional(boolean()),
|
|
@@ -47,10 +48,8 @@ export function useScriptPostHog(_options) {
|
|
|
47
48
|
return;
|
|
48
49
|
}
|
|
49
50
|
const region = options?.region || "us";
|
|
50
|
-
const apiHost = region === "eu" ? "https://eu.i.posthog.com" : "https://us.i.posthog.com";
|
|
51
|
-
console.log("[PostHog] Starting dynamic import of posthog-js...");
|
|
51
|
+
const apiHost = options?.apiHost || (region === "eu" ? "https://eu.i.posthog.com" : "https://us.i.posthog.com");
|
|
52
52
|
window.__posthogInitPromise = import("posthog-js").then(({ default: posthog }) => {
|
|
53
|
-
console.log("[PostHog] posthog-js imported successfully");
|
|
54
53
|
const config = {
|
|
55
54
|
api_host: apiHost,
|
|
56
55
|
...options?.config
|
|
@@ -63,25 +62,20 @@ export function useScriptPostHog(_options) {
|
|
|
63
62
|
config.capture_pageleave = options.capturePageleave;
|
|
64
63
|
if (typeof options?.disableSessionRecording === "boolean")
|
|
65
64
|
config.disable_session_recording = options.disableSessionRecording;
|
|
66
|
-
console.log("[PostHog] Calling posthog.init with apiKey:", options.apiKey, "config:", config);
|
|
67
65
|
const instance = posthog.init(options.apiKey, config);
|
|
68
66
|
if (!instance) {
|
|
69
67
|
logger.error("PostHog init returned undefined - initialization failed");
|
|
70
68
|
delete window._posthogQueue;
|
|
71
69
|
return void 0;
|
|
72
70
|
}
|
|
73
|
-
console.log("[PostHog] posthog.init succeeded, instance:", instance);
|
|
74
71
|
window.posthog = instance;
|
|
75
72
|
if (window._posthogQueue && window._posthogQueue.length > 0) {
|
|
76
|
-
console.log("[PostHog] Flushing", window._posthogQueue.length, "queued calls");
|
|
77
73
|
window._posthogQueue.forEach((q) => window.posthog[q.prop]?.(...q.args));
|
|
78
74
|
delete window._posthogQueue;
|
|
79
75
|
}
|
|
80
|
-
console.log("[PostHog] Initialization complete!");
|
|
81
76
|
return window.posthog;
|
|
82
77
|
}).catch((e) => {
|
|
83
78
|
logger.error("Failed to load posthog-js:", e);
|
|
84
|
-
console.error("[PostHog] Import/init error:", e);
|
|
85
79
|
delete window._posthogQueue;
|
|
86
80
|
return void 0;
|
|
87
81
|
});
|
|
@@ -4,20 +4,20 @@ import { useStorage, useNitroApp } from "nitropack/runtime";
|
|
|
4
4
|
import { hash } from "ohash";
|
|
5
5
|
import { rewriteScriptUrls } from "../utils/pure.js";
|
|
6
6
|
import {
|
|
7
|
-
FINGERPRINT_HEADERS,
|
|
8
|
-
IP_HEADERS,
|
|
9
7
|
SENSITIVE_HEADERS,
|
|
10
8
|
anonymizeIP,
|
|
11
9
|
normalizeLanguage,
|
|
12
10
|
normalizeUserAgent,
|
|
13
|
-
stripPayloadFingerprinting
|
|
11
|
+
stripPayloadFingerprinting,
|
|
12
|
+
resolvePrivacy,
|
|
13
|
+
mergePrivacy
|
|
14
14
|
} from "./utils/privacy.js";
|
|
15
|
-
function stripQueryFingerprinting(query) {
|
|
16
|
-
const stripped = stripPayloadFingerprinting(query);
|
|
15
|
+
function stripQueryFingerprinting(query, privacy) {
|
|
16
|
+
const stripped = stripPayloadFingerprinting(query, privacy);
|
|
17
17
|
const params = new URLSearchParams();
|
|
18
18
|
for (const [key, value] of Object.entries(stripped)) {
|
|
19
19
|
if (value !== void 0 && value !== null) {
|
|
20
|
-
params.set(key, String(value));
|
|
20
|
+
params.set(key, typeof value === "object" ? JSON.stringify(value) : String(value));
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
return params.toString();
|
|
@@ -32,7 +32,7 @@ export default defineEventHandler(async (event) => {
|
|
|
32
32
|
statusMessage: "First-party proxy not configured"
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
|
-
const { routes, privacy, cacheTtl = 3600, debug = import.meta.dev } = proxyConfig;
|
|
35
|
+
const { routes, privacy: globalPrivacy, routePrivacy, cacheTtl = 3600, debug = import.meta.dev } = proxyConfig;
|
|
36
36
|
const path = event.path;
|
|
37
37
|
const log = debug ? (message, ...args) => {
|
|
38
38
|
console.debug(message, ...args);
|
|
@@ -40,17 +40,19 @@ export default defineEventHandler(async (event) => {
|
|
|
40
40
|
};
|
|
41
41
|
let targetBase;
|
|
42
42
|
let matchedPrefix;
|
|
43
|
+
let matchedRoutePattern;
|
|
43
44
|
const sortedRoutes = Object.entries(routes).sort((a, b) => b[0].length - a[0].length);
|
|
44
45
|
for (const [routePattern, target] of sortedRoutes) {
|
|
45
46
|
const prefix = routePattern.replace(/\/\*\*$/, "");
|
|
46
47
|
if (path.startsWith(prefix)) {
|
|
47
48
|
targetBase = target.replace(/\/\*\*$/, "");
|
|
48
49
|
matchedPrefix = prefix;
|
|
50
|
+
matchedRoutePattern = routePattern;
|
|
49
51
|
log("[proxy] Matched:", prefix, "->", targetBase);
|
|
50
52
|
break;
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
|
-
if (!targetBase || !matchedPrefix) {
|
|
55
|
+
if (!targetBase || !matchedPrefix || !matchedRoutePattern) {
|
|
54
56
|
log("[proxy] No match for path:", path);
|
|
55
57
|
throw createError({
|
|
56
58
|
statusCode: 404,
|
|
@@ -58,54 +60,68 @@ export default defineEventHandler(async (event) => {
|
|
|
58
60
|
message: `No proxy target found for path: ${path}`
|
|
59
61
|
});
|
|
60
62
|
}
|
|
63
|
+
const perScriptInput = routePrivacy[matchedRoutePattern];
|
|
64
|
+
if (debug && perScriptInput === void 0) {
|
|
65
|
+
log("[proxy] WARNING: No privacy config for route", matchedRoutePattern, "\u2014 defaulting to full anonymization");
|
|
66
|
+
}
|
|
67
|
+
const perScriptResolved = resolvePrivacy(perScriptInput ?? true);
|
|
68
|
+
const privacy = globalPrivacy !== void 0 ? mergePrivacy(perScriptResolved, globalPrivacy) : perScriptResolved;
|
|
69
|
+
const anyPrivacy = privacy.ip || privacy.userAgent || privacy.language || privacy.screen || privacy.timezone || privacy.hardware;
|
|
61
70
|
let targetPath = path.slice(matchedPrefix.length);
|
|
62
71
|
if (targetPath && !targetPath.startsWith("/")) {
|
|
63
72
|
targetPath = "/" + targetPath;
|
|
64
73
|
}
|
|
65
74
|
let targetUrl = targetBase + targetPath;
|
|
66
|
-
if (
|
|
75
|
+
if (anyPrivacy) {
|
|
67
76
|
const query = getQuery(event);
|
|
68
77
|
if (Object.keys(query).length > 0) {
|
|
69
|
-
const strippedQuery = stripQueryFingerprinting(query);
|
|
78
|
+
const strippedQuery = stripQueryFingerprinting(query, privacy);
|
|
70
79
|
const basePath = targetUrl.split("?")[0] || targetUrl;
|
|
71
80
|
targetUrl = strippedQuery ? `${basePath}?${strippedQuery}` : basePath;
|
|
72
81
|
}
|
|
73
82
|
}
|
|
74
83
|
const originalHeaders = getHeaders(event);
|
|
75
84
|
const headers = {};
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
85
|
+
for (const [key, value] of Object.entries(originalHeaders)) {
|
|
86
|
+
if (!value) continue;
|
|
87
|
+
const lowerKey = key.toLowerCase();
|
|
88
|
+
if (SENSITIVE_HEADERS.includes(lowerKey)) continue;
|
|
89
|
+
if (anyPrivacy && lowerKey === "content-length") continue;
|
|
90
|
+
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") {
|
|
91
|
+
if (privacy.ip) continue;
|
|
92
|
+
headers[lowerKey] = value;
|
|
93
|
+
continue;
|
|
81
94
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (lowerKey === "user-agent") {
|
|
94
|
-
headers[key] = normalizeUserAgent(value);
|
|
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
|
-
}
|
|
95
|
+
if (lowerKey === "user-agent") {
|
|
96
|
+
headers[key] = privacy.userAgent ? normalizeUserAgent(value) : value;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (lowerKey === "accept-language") {
|
|
100
|
+
headers[key] = privacy.language ? normalizeLanguage(value) : value;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (lowerKey === "sec-ch-ua" || lowerKey === "sec-ch-ua-full-version-list") {
|
|
104
|
+
headers[lowerKey] = privacy.hardware ? value.replace(/;v="(\d+)\.[^"]*"/g, ';v="$1"') : value;
|
|
105
|
+
continue;
|
|
104
106
|
}
|
|
107
|
+
if (lowerKey === "sec-ch-ua-platform-version" || lowerKey === "sec-ch-ua-arch" || lowerKey === "sec-ch-ua-model" || lowerKey === "sec-ch-ua-bitness") {
|
|
108
|
+
if (privacy.hardware) continue;
|
|
109
|
+
headers[lowerKey] = value;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
headers[key] = value;
|
|
113
|
+
}
|
|
114
|
+
if (!headers["x-forwarded-for"]) {
|
|
105
115
|
const clientIP = getRequestIP(event, { xForwardedFor: true });
|
|
106
116
|
if (clientIP) {
|
|
107
|
-
|
|
117
|
+
if (privacy.ip) {
|
|
118
|
+
headers["x-forwarded-for"] = anonymizeIP(clientIP);
|
|
119
|
+
} else {
|
|
120
|
+
headers["x-forwarded-for"] = clientIP;
|
|
121
|
+
}
|
|
108
122
|
}
|
|
123
|
+
} else if (privacy.ip) {
|
|
124
|
+
headers["x-forwarded-for"] = headers["x-forwarded-for"].split(",").map((ip) => anonymizeIP(ip.trim())).join(", ");
|
|
109
125
|
}
|
|
110
126
|
let body;
|
|
111
127
|
let rawBody;
|
|
@@ -114,9 +130,9 @@ export default defineEventHandler(async (event) => {
|
|
|
114
130
|
const originalQuery = getQuery(event);
|
|
115
131
|
if (method === "POST" || method === "PUT" || method === "PATCH") {
|
|
116
132
|
rawBody = await readBody(event);
|
|
117
|
-
if (
|
|
133
|
+
if (anyPrivacy && rawBody) {
|
|
118
134
|
if (typeof rawBody === "object") {
|
|
119
|
-
body = stripPayloadFingerprinting(rawBody);
|
|
135
|
+
body = stripPayloadFingerprinting(rawBody, privacy);
|
|
120
136
|
} else if (typeof rawBody === "string") {
|
|
121
137
|
if (rawBody.startsWith("{") || rawBody.startsWith("[")) {
|
|
122
138
|
let parsed = null;
|
|
@@ -125,7 +141,7 @@ export default defineEventHandler(async (event) => {
|
|
|
125
141
|
} catch {
|
|
126
142
|
}
|
|
127
143
|
if (parsed && typeof parsed === "object") {
|
|
128
|
-
body = stripPayloadFingerprinting(parsed);
|
|
144
|
+
body = stripPayloadFingerprinting(parsed, privacy);
|
|
129
145
|
} else {
|
|
130
146
|
body = rawBody;
|
|
131
147
|
}
|
|
@@ -135,7 +151,7 @@ export default defineEventHandler(async (event) => {
|
|
|
135
151
|
params.forEach((value, key) => {
|
|
136
152
|
obj[key] = value;
|
|
137
153
|
});
|
|
138
|
-
const stripped = stripPayloadFingerprinting(obj);
|
|
154
|
+
const stripped = stripPayloadFingerprinting(obj, privacy);
|
|
139
155
|
const stringified = {};
|
|
140
156
|
for (const [k, v] of Object.entries(stripped)) {
|
|
141
157
|
if (v === void 0 || v === null) continue;
|
|
@@ -165,7 +181,7 @@ export default defineEventHandler(async (event) => {
|
|
|
165
181
|
},
|
|
166
182
|
stripped: {
|
|
167
183
|
headers,
|
|
168
|
-
query:
|
|
184
|
+
query: anyPrivacy ? stripPayloadFingerprinting(originalQuery, privacy) : originalQuery,
|
|
169
185
|
body: body ?? null
|
|
170
186
|
}
|
|
171
187
|
});
|
|
@@ -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>;
|