@nuxt/scripts 1.0.0-beta.30 → 1.0.0-beta.31

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.
Files changed (27) hide show
  1. package/dist/client/200.html +1 -1
  2. package/dist/client/404.html +1 -1
  3. package/dist/client/_nuxt/{CYlYSSNW.js → 6CwTUC2b.js} +1 -1
  4. package/dist/client/_nuxt/{D5FIkDae.js → B71AlSZ1.js} +1 -1
  5. package/dist/client/_nuxt/{AwAKM0sG.js → BYGJV5dd.js} +1 -1
  6. package/dist/client/_nuxt/{Bl23o3st.js → V4W-T8W6.js} +4 -4
  7. package/dist/client/_nuxt/builds/latest.json +1 -1
  8. package/dist/client/_nuxt/builds/meta/70b59a3e-a025-4a77-a25a-dfadf5b1749d.json +1 -0
  9. package/dist/client/index.html +1 -1
  10. package/dist/module.d.mts +2 -2
  11. package/dist/module.d.ts +2 -2
  12. package/dist/module.json +1 -1
  13. package/dist/module.mjs +43 -70
  14. package/dist/registry.mjs +9 -9
  15. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.d.vue.ts +5 -2
  16. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue +11 -2
  17. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsMarkerClusterer.vue.d.ts +5 -2
  18. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.d.vue.ts +14 -0
  19. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue +50 -1
  20. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsOverlayView.vue.d.ts +14 -0
  21. package/dist/runtime/server/proxy-handler.js +25 -35
  22. package/dist/shared/scripts.D7e2ENu6.mjs +211 -0
  23. package/dist/stats.mjs +6 -14
  24. package/dist/types-source.mjs +15 -1
  25. package/package.json +2 -2
  26. package/dist/client/_nuxt/builds/meta/f0b4dd20-8496-4003-b7a3-05cbae515923.json +0 -1
  27. package/dist/shared/scripts.ViOoYQXH.mjs +0 -381
@@ -11,18 +11,8 @@ import {
11
11
  stripPayloadFingerprinting
12
12
  } from "./utils/privacy.js";
13
13
  const COMPRESSION_RE = /gzip|deflate|br|compress|base64/i;
14
- const ROUTE_WILDCARD_RE = /\/\*\*$/;
15
14
  const CLIENT_HINT_VERSION_RE = /;v="(\d+)\.[^"]*"/g;
16
15
  const SKIP_RESPONSE_HEADERS = /* @__PURE__ */ new Set(["set-cookie", "transfer-encoding", "content-encoding", "content-length"]);
17
- let sortedRoutesCache;
18
- function getSortedRoutes(routes) {
19
- const key = JSON.stringify(routes);
20
- if (sortedRoutesCache?.key === key)
21
- return sortedRoutesCache.sorted;
22
- const sorted = Object.entries(routes).sort((a, b) => b[0].length - a[0].length);
23
- sortedRoutesCache = { key, sorted };
24
- return sorted;
25
- }
26
16
  function stripQueryFingerprinting(query, privacy) {
27
17
  const stripped = stripPayloadFingerprinting(query, privacy);
28
18
  const params = new URLSearchParams();
@@ -42,37 +32,41 @@ export default defineEventHandler(async (event) => {
42
32
  statusMessage: "First-party proxy not configured"
43
33
  });
44
34
  }
45
- const { routes, privacy: globalPrivacy, routePrivacy, debug = import.meta.dev } = proxyConfig;
35
+ const { proxyPrefix, domainPrivacy, privacy: globalPrivacy, debug = import.meta.dev } = proxyConfig;
46
36
  const path = event.path;
47
37
  const log = debug ? (message, ...args) => {
48
38
  console.debug(message, ...args);
49
39
  } : () => {
50
40
  };
51
- let targetBase;
52
- let matchedPrefix;
53
- let matchedRoutePattern;
54
- for (const [routePattern, target] of getSortedRoutes(routes)) {
55
- const prefix = routePattern.replace(ROUTE_WILDCARD_RE, "");
56
- if (path.startsWith(prefix)) {
57
- targetBase = target.replace(ROUTE_WILDCARD_RE, "");
58
- matchedPrefix = prefix;
59
- matchedRoutePattern = routePattern;
60
- log("[proxy] Matched:", prefix, "->", targetBase);
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;
61
57
  break;
62
58
  }
63
59
  }
64
- if (!targetBase || !matchedPrefix || !matchedRoutePattern) {
65
- log("[proxy] No match for path:", path);
60
+ if (perScriptInput === void 0) {
61
+ log("[proxy] Rejected: domain not in allowlist:", domain);
66
62
  throw createError({
67
- statusCode: 404,
68
- statusMessage: "No proxy route matched",
69
- message: `No proxy target found for path: ${path}`
63
+ statusCode: 403,
64
+ statusMessage: "Domain not allowed",
65
+ message: `Proxy domain not in allowlist: ${domain}`
70
66
  });
71
67
  }
72
- const perScriptInput = routePrivacy[matchedRoutePattern];
73
- if (debug && perScriptInput === void 0) {
74
- log("[proxy] WARNING: No privacy config for route", matchedRoutePattern, "\u2014 defaulting to full anonymization");
75
- }
68
+ const targetBase = `https://${domain}`;
69
+ log("[proxy] Matched:", domain, "->", targetBase);
76
70
  const perScriptResolved = resolvePrivacy(perScriptInput ?? true);
77
71
  const privacy = globalPrivacy !== void 0 ? mergePrivacy(perScriptResolved, globalPrivacy) : perScriptResolved;
78
72
  const anyPrivacy = privacy.ip || privacy.userAgent || privacy.language || privacy.screen || privacy.timezone || privacy.hardware;
@@ -83,11 +77,7 @@ export default defineEventHandler(async (event) => {
83
77
  const isBinaryBody = Boolean(
84
78
  originalHeaders["content-encoding"] || contentType.includes("octet-stream") || compressionParam && COMPRESSION_RE.test(compressionParam)
85
79
  );
86
- let targetPath = path.slice(matchedPrefix.length);
87
- if (targetPath && !targetPath.startsWith("/")) {
88
- targetPath = `/${targetPath}`;
89
- }
90
- let targetUrl = targetBase + targetPath;
80
+ let targetUrl = targetBase + remainingPath;
91
81
  let strippedQueryRecord;
92
82
  if (anyPrivacy) {
93
83
  if (Object.keys(originalQuery).length > 0) {
@@ -0,0 +1,211 @@
1
+ const FATHOM_SELF_HOSTED_RE = /\.src\.indexOf\("cdn\.usefathom\.com"\)\s*<\s*0/;
2
+ const RYBBIT_HOST_SPLIT_RE = /\w+\.split\(["']\/script\.js["']\)\[0\]/g;
3
+ const PRIVACY_NONE = { ip: false, userAgent: false, language: false, screen: false, timezone: false, hardware: false };
4
+ const PRIVACY_FULL = { ip: true, userAgent: true, language: true, screen: true, timezone: true, hardware: true };
5
+ const PRIVACY_HEATMAP = { ip: true, userAgent: false, language: true, screen: false, timezone: false, hardware: true };
6
+ const PRIVACY_IP_ONLY = { ip: true, userAgent: false, language: false, screen: false, timezone: false, hardware: false };
7
+ function buildProxyConfig(_proxyPrefix) {
8
+ return {
9
+ googleAnalytics: {
10
+ domains: [
11
+ "www.google-analytics.com",
12
+ "analytics.google.com",
13
+ "stats.g.doubleclick.net",
14
+ "pagead2.googlesyndication.com",
15
+ "www.googleadservices.com",
16
+ "googleads.g.doubleclick.net"
17
+ ],
18
+ privacy: PRIVACY_HEATMAP
19
+ },
20
+ googleTagManager: {
21
+ domains: ["www.googletagmanager.com"],
22
+ privacy: PRIVACY_NONE
23
+ },
24
+ metaPixel: {
25
+ domains: [
26
+ "connect.facebook.net",
27
+ "www.facebook.com",
28
+ "facebook.com",
29
+ "pixel.facebook.com"
30
+ ],
31
+ privacy: PRIVACY_FULL
32
+ },
33
+ tiktokPixel: {
34
+ domains: ["analytics.tiktok.com"],
35
+ privacy: PRIVACY_FULL
36
+ },
37
+ segment: {
38
+ domains: ["api.segment.io", "cdn.segment.com"],
39
+ privacy: PRIVACY_NONE
40
+ },
41
+ xPixel: {
42
+ domains: ["analytics.twitter.com", "static.ads-twitter.com", "t.co"],
43
+ privacy: PRIVACY_FULL
44
+ },
45
+ snapchatPixel: {
46
+ domains: ["sc-static.net", "tr.snapchat.com", "pixel.tapad.com"],
47
+ privacy: PRIVACY_FULL
48
+ },
49
+ redditPixel: {
50
+ domains: ["www.redditstatic.com", "alb.reddit.com", "pixel-config.reddit.com"],
51
+ privacy: PRIVACY_FULL
52
+ },
53
+ clarity: {
54
+ domains: [
55
+ "www.clarity.ms",
56
+ "scripts.clarity.ms",
57
+ "d.clarity.ms",
58
+ "e.clarity.ms",
59
+ "k.clarity.ms"
60
+ ],
61
+ privacy: PRIVACY_HEATMAP
62
+ },
63
+ posthog: {
64
+ domains: [
65
+ "us-assets.i.posthog.com",
66
+ "us.i.posthog.com",
67
+ "eu-assets.i.posthog.com",
68
+ "eu.i.posthog.com"
69
+ ],
70
+ privacy: PRIVACY_NONE,
71
+ autoInject: {
72
+ configField: "apiHost",
73
+ computeValue: (proxyPrefix, config) => {
74
+ const region = config.region || "us";
75
+ const host = region === "eu" ? "eu.i.posthog.com" : "us.i.posthog.com";
76
+ return `${proxyPrefix}/${host}`;
77
+ }
78
+ }
79
+ },
80
+ hotjar: {
81
+ domains: [
82
+ "static.hotjar.com",
83
+ "script.hotjar.com",
84
+ "vars.hotjar.com",
85
+ "in.hotjar.com",
86
+ "vc.hotjar.com",
87
+ "vc.hotjar.io",
88
+ "metrics.hotjar.io",
89
+ "insights.hotjar.com",
90
+ "ask.hotjar.io",
91
+ "events.hotjar.io",
92
+ "identify.hotjar.com",
93
+ "surveystats.hotjar.io"
94
+ ],
95
+ privacy: PRIVACY_HEATMAP
96
+ },
97
+ plausibleAnalytics: {
98
+ domains: ["plausible.io"],
99
+ privacy: PRIVACY_NONE,
100
+ autoInject: {
101
+ configField: "endpoint",
102
+ computeValue: (proxyPrefix) => `${proxyPrefix}/plausible.io/api/event`
103
+ }
104
+ },
105
+ cloudflareWebAnalytics: {
106
+ domains: ["static.cloudflareinsights.com", "cloudflareinsights.com"],
107
+ privacy: PRIVACY_NONE
108
+ },
109
+ rybbitAnalytics: {
110
+ domains: ["app.rybbit.io"],
111
+ privacy: PRIVACY_NONE,
112
+ autoInject: {
113
+ configField: "analyticsHost",
114
+ computeValue: (proxyPrefix) => `${proxyPrefix}/app.rybbit.io/api`
115
+ },
116
+ postProcess(output, rewrites) {
117
+ const rybbitRewrite = rewrites.find((r) => r.from === "app.rybbit.io");
118
+ if (rybbitRewrite) {
119
+ output = output.replace(
120
+ RYBBIT_HOST_SPLIT_RE,
121
+ `self.location.origin+"${rybbitRewrite.to}/api"`
122
+ );
123
+ }
124
+ return output;
125
+ }
126
+ },
127
+ umamiAnalytics: {
128
+ domains: ["cloud.umami.is", "api-gateway.umami.dev"],
129
+ privacy: PRIVACY_NONE,
130
+ autoInject: {
131
+ configField: "hostUrl",
132
+ computeValue: (proxyPrefix) => `${proxyPrefix}/cloud.umami.is`
133
+ }
134
+ },
135
+ databuddyAnalytics: {
136
+ domains: ["cdn.databuddy.cc", "basket.databuddy.cc"],
137
+ privacy: PRIVACY_NONE,
138
+ autoInject: {
139
+ configField: "apiUrl",
140
+ computeValue: (proxyPrefix) => `${proxyPrefix}/basket.databuddy.cc`
141
+ }
142
+ },
143
+ fathomAnalytics: {
144
+ domains: ["cdn.usefathom.com"],
145
+ privacy: PRIVACY_NONE,
146
+ postProcess(output) {
147
+ return output.replace(
148
+ FATHOM_SELF_HOSTED_RE,
149
+ '.src.indexOf("cdn.usefathom.com")<-1'
150
+ );
151
+ }
152
+ },
153
+ intercom: {
154
+ domains: [
155
+ "widget.intercom.io",
156
+ "api-iam.intercom.io",
157
+ "api-iam.eu.intercom.io",
158
+ "api-iam.au.intercom.io",
159
+ "js.intercomcdn.com",
160
+ "downloads.intercomcdn.com",
161
+ "video-messages.intercomcdn.com"
162
+ ],
163
+ privacy: PRIVACY_IP_ONLY
164
+ },
165
+ crisp: {
166
+ domains: [
167
+ "client.crisp.chat",
168
+ "client.relay.crisp.chat",
169
+ "assets.crisp.chat",
170
+ "go.crisp.chat",
171
+ "image.crisp.chat"
172
+ ],
173
+ privacy: PRIVACY_IP_ONLY
174
+ },
175
+ vercelAnalytics: {
176
+ domains: ["va.vercel-scripts.com"],
177
+ privacy: PRIVACY_NONE
178
+ },
179
+ gravatar: {
180
+ domains: ["secure.gravatar.com", "gravatar.com"],
181
+ privacy: PRIVACY_IP_ONLY
182
+ },
183
+ carbonAds: {
184
+ domains: ["cdn.carbonads.com"],
185
+ privacy: PRIVACY_NONE
186
+ },
187
+ lemonSqueezy: {
188
+ domains: ["assets.lemonsqueezy.com"],
189
+ privacy: PRIVACY_NONE
190
+ },
191
+ matomoAnalytics: {
192
+ domains: ["cdn.matomo.cloud"],
193
+ privacy: PRIVACY_NONE
194
+ },
195
+ // stripe, paypal: proxy: false in registry (need fingerprinting for fraud detection)
196
+ youtubePlayer: {
197
+ domains: ["www.youtube.com"],
198
+ privacy: PRIVACY_IP_ONLY
199
+ },
200
+ vimeoPlayer: {
201
+ domains: ["player.vimeo.com"],
202
+ privacy: PRIVACY_IP_ONLY
203
+ }
204
+ // googleRecaptcha, googleSignIn: proxy: false in registry (need fingerprinting for bot detection / auth)
205
+ };
206
+ }
207
+ function getAllProxyConfigs(proxyPrefix) {
208
+ return buildProxyConfig();
209
+ }
210
+
211
+ export { getAllProxyConfigs as g };
package/dist/stats.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { g as getAllProxyConfigs } from './shared/scripts.ViOoYQXH.mjs';
1
+ import { g as getAllProxyConfigs } from './shared/scripts.D7e2ENu6.mjs';
2
2
 
3
3
  const scriptMeta = {
4
4
  // Analytics
@@ -3767,7 +3767,6 @@ function computePerformanceRating(perf, transferKb, network, cwv) {
3767
3767
  }
3768
3768
  };
3769
3769
  }
3770
- const DOMAIN_RE = /^https?:\/\/([^/]+)/;
3771
3770
  const USE_SCRIPT_RE = /^useScript/;
3772
3771
  const WORD_SPLIT_RE = /[\s-]+/;
3773
3772
  function computePrivacyLevel(privacy) {
@@ -3780,14 +3779,8 @@ function computePrivacyLevel(privacy) {
3780
3779
  return "partial";
3781
3780
  return "none";
3782
3781
  }
3783
- function extractDomains(routes) {
3784
- const domains = /* @__PURE__ */ new Set();
3785
- for (const { proxy } of Object.values(routes)) {
3786
- const match = proxy.match(DOMAIN_RE);
3787
- if (match?.[1])
3788
- domains.add(match[1]);
3789
- }
3790
- return [...domains].sort();
3782
+ function extractDomains(proxyDomains) {
3783
+ return [...proxyDomains].sort();
3791
3784
  }
3792
3785
  function deriveMetaKey(importName, label) {
3793
3786
  if (importName) {
@@ -3805,7 +3798,7 @@ function deriveMetaKey(importName, label) {
3805
3798
  async function getScriptStats() {
3806
3799
  const { registry } = await import('./registry.mjs');
3807
3800
  const entries = await registry();
3808
- const proxyConfigs = getAllProxyConfigs("/_scripts/p");
3801
+ const proxyConfigs = getAllProxyConfigs();
3809
3802
  const sizes = scriptSizes;
3810
3803
  return entries.map((entry) => {
3811
3804
  const id = entry.registryKey || deriveMetaKey(entry.import?.name, entry.label);
@@ -3819,9 +3812,8 @@ async function getScriptStats() {
3819
3812
  else if (!entry.src && typeof entry.scriptBundling === "function")
3820
3813
  loadingMethod = "dynamic";
3821
3814
  const privacy = proxyConfig?.privacy ?? null;
3822
- const routes = proxyConfig?.routes ?? {};
3823
- const domains = extractDomains(routes);
3824
- const endpoints = Object.keys(routes).length;
3815
+ const domains = extractDomains(proxyConfig?.domains ?? []);
3816
+ const endpoints = domains.length;
3825
3817
  const emptyApis = {};
3826
3818
  const emptyNetwork = { requestCount: 0, domains: [], outboundBytes: 0, inboundBytes: 0, injectedElements: [] };
3827
3819
  const emptyPerf = { taskDurationMs: 0, scriptDurationMs: 0, heapDeltaKb: 0 };
@@ -276,7 +276,7 @@ const types = {
276
276
  {
277
277
  name: "ScriptGoogleMapsOverlayViewProps",
278
278
  kind: "interface",
279
- code: "interface ScriptGoogleMapsOverlayViewProps {\n /**\n * Geographic position for the overlay. Falls back to parent marker position if omitted.\n * @see https://developers.google.com/maps/documentation/javascript/reference/overlay-view#OverlayView\n */\n position?: google.maps.LatLngLiteral\n /**\n * Anchor point of the overlay relative to its position.\n * @default 'bottom-center'\n */\n anchor?: OverlayAnchor\n /**\n * Pixel offset from the anchor position.\n */\n offset?: { x: number, y: number }\n /**\n * The map pane on which to render the overlay.\n * @default 'floatPane'\n * @see https://developers.google.com/maps/documentation/javascript/reference/overlay-view#MapPanes\n */\n pane?: OverlayPane\n /**\n * CSS z-index for the overlay element.\n */\n zIndex?: number\n /**\n * Whether to block map click and gesture events from passing through the overlay.\n * @default true\n */\n blockMapInteraction?: boolean\n}"
279
+ code: "interface ScriptGoogleMapsOverlayViewProps {\n /**\n * Geographic position for the overlay. Falls back to parent marker position if omitted.\n * @see https://developers.google.com/maps/documentation/javascript/reference/overlay-view#OverlayView\n */\n position?: google.maps.LatLngLiteral\n /**\n * Anchor point of the overlay relative to its position.\n * @default 'bottom-center'\n */\n anchor?: OverlayAnchor\n /**\n * Pixel offset from the anchor position.\n */\n offset?: { x: number, y: number }\n /**\n * The map pane on which to render the overlay.\n * @default 'floatPane'\n * @see https://developers.google.com/maps/documentation/javascript/reference/overlay-view#MapPanes\n */\n pane?: OverlayPane\n /**\n * CSS z-index for the overlay element.\n */\n zIndex?: number\n /**\n * Whether to block map click and gesture events from passing through the overlay.\n * @default true\n */\n blockMapInteraction?: boolean\n /**\n * Pan the map so the overlay is fully visible when opened, similar to InfoWindow behavior.\n * Set to `true` for default 40px padding, or a number for custom padding.\n * @default true\n */\n panOnOpen?: boolean | number\n /**\n * Automatically hide the overlay when its parent marker joins a cluster (on zoom out).\n * Only applies when nested inside a ScriptGoogleMapsMarkerClusterer.\n * @default true\n */\n hideWhenClustered?: boolean\n}"
280
280
  },
281
281
  {
282
282
  name: "ScriptGoogleMapsPinElementProps",
@@ -2697,6 +2697,20 @@ const schemaFields = {
2697
2697
  description: "Whether to block map click and gesture events from passing through the overlay.",
2698
2698
  defaultValue: "true"
2699
2699
  },
2700
+ {
2701
+ name: "panOnOpen",
2702
+ type: "boolean | number",
2703
+ required: false,
2704
+ description: "Pan the map so the overlay is fully visible when opened, similar to InfoWindow behavior. Set to `true` for default 40px padding, or a number for custom padding.",
2705
+ defaultValue: "true"
2706
+ },
2707
+ {
2708
+ name: "hideWhenClustered",
2709
+ type: "boolean",
2710
+ required: false,
2711
+ description: "Automatically hide the overlay when its parent marker joins a cluster (on zoom out). Only applies when nested inside a ScriptGoogleMapsMarkerClusterer.",
2712
+ defaultValue: "true"
2713
+ },
2700
2714
  {
2701
2715
  name: "v-model:open",
2702
2716
  type: "boolean",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nuxt/scripts",
3
3
  "type": "module",
4
- "version": "1.0.0-beta.30",
4
+ "version": "1.0.0-beta.31",
5
5
  "description": "Load third-party scripts with better performance, privacy and DX in Nuxt Apps.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -144,7 +144,7 @@
144
144
  "vue": "^3.5.30",
145
145
  "vue-router": "^5.0.4",
146
146
  "vue-tsc": "^3.2.6",
147
- "@nuxt/scripts": "1.0.0-beta.30"
147
+ "@nuxt/scripts": "1.0.0-beta.31"
148
148
  },
149
149
  "resolutions": {
150
150
  "@nuxt/scripts": "workspace:*"
@@ -1 +0,0 @@
1
- {"id":"f0b4dd20-8496-4003-b7a3-05cbae515923","timestamp":1774019040423,"prerendered":[]}