@nuxt/scripts 1.1.0 → 1.2.0

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 (69) hide show
  1. package/dist/devtools-client/200.html +1 -1
  2. package/dist/devtools-client/404.html +1 -1
  3. package/dist/devtools-client/_nuxt/BTXw0v4h.js +1 -0
  4. package/dist/devtools-client/_nuxt/Bf6N2ObH.js +1 -0
  5. package/dist/devtools-client/_nuxt/C5Bq9Qgz.js +1 -0
  6. package/dist/devtools-client/_nuxt/CSw0kWhI.js +1 -0
  7. package/dist/devtools-client/_nuxt/DWvCkA0U.js +195 -0
  8. package/dist/devtools-client/_nuxt/TPgAGQc8.js +1 -0
  9. package/dist/devtools-client/_nuxt/builds/latest.json +1 -1
  10. package/dist/devtools-client/_nuxt/builds/meta/df290ad6-a770-4973-bb7f-110a630b368c.json +1 -0
  11. package/dist/devtools-client/_nuxt/dvDf1_6X.js +1 -0
  12. package/dist/devtools-client/_nuxt/entry.VwiedW44.css +1 -0
  13. package/dist/devtools-client/_nuxt/error-404.DBJH6QEN.css +1 -0
  14. package/dist/devtools-client/_nuxt/error-500.BIIPjeaA.css +1 -0
  15. package/dist/devtools-client/_nuxt/{first-party.C8Ha4JLM.css → first-party.D_SBdnka.css} +1 -1
  16. package/dist/devtools-client/_nuxt/index.DIj4bh55.css +1 -0
  17. package/dist/devtools-client/_nuxt/{registry.B9lnjF_b.css → registry.CDbTq1x3.css} +1 -1
  18. package/dist/devtools-client/docs/index.html +1 -1
  19. package/dist/devtools-client/first-party/index.html +1 -1
  20. package/dist/devtools-client/index.html +1 -1
  21. package/dist/devtools-client/registry/index.html +1 -1
  22. package/dist/module.d.mts +5 -1
  23. package/dist/module.d.ts +5 -1
  24. package/dist/module.json +1 -1
  25. package/dist/module.mjs +35 -4
  26. package/dist/registry.mjs +16 -3
  27. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsHeatmapLayer.d.vue.ts +18 -1
  28. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsHeatmapLayer.vue +7 -1
  29. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsHeatmapLayer.vue.d.ts +18 -1
  30. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.d.vue.ts +9 -0
  31. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.vue +6 -5
  32. package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.vue.d.ts +9 -0
  33. package/dist/runtime/components/ScriptGravatar.vue +2 -0
  34. package/dist/runtime/components/ScriptLoadingIndicator.vue +1 -1
  35. package/dist/runtime/components/ScriptVimeoPlayer.vue +27 -4
  36. package/dist/runtime/components/ScriptYouTubePlayer.vue +25 -4
  37. package/dist/runtime/composables/useScript.js +4 -3
  38. package/dist/runtime/registry/schemas.d.ts +76 -0
  39. package/dist/runtime/registry/schemas.js +82 -2
  40. package/dist/runtime/registry/speedcurve.d.ts +21 -0
  41. package/dist/runtime/registry/speedcurve.js +93 -0
  42. package/dist/runtime/registry/stripe.js +1 -1
  43. package/dist/runtime/registry/tiktok-pixel.d.ts +52 -13
  44. package/dist/runtime/registry/tiktok-pixel.js +46 -29
  45. package/dist/runtime/server/instagram-embed.js +37 -10
  46. package/dist/runtime/server/proxy-handler.d.ts +1 -0
  47. package/dist/runtime/server/proxy-handler.js +16 -0
  48. package/dist/runtime/server/utils/instagram-embed.d.ts +1 -0
  49. package/dist/runtime/server/utils/instagram-embed.js +5 -0
  50. package/dist/runtime/types.d.ts +3 -1
  51. package/dist/runtime/utils/after-next-paint.d.ts +1 -0
  52. package/dist/runtime/utils/after-next-paint.js +3 -0
  53. package/dist/stats.d.mts +1 -1
  54. package/dist/stats.d.ts +1 -1
  55. package/dist/stats.mjs +5 -0
  56. package/dist/types-source.mjs +127 -6
  57. package/package.json +12 -7
  58. package/dist/devtools-client/_nuxt/1wb58MKb.js +0 -1
  59. package/dist/devtools-client/_nuxt/BbLmrp_o.js +0 -1
  60. package/dist/devtools-client/_nuxt/Cx46cS8a.js +0 -1
  61. package/dist/devtools-client/_nuxt/D-M51CV3.js +0 -1
  62. package/dist/devtools-client/_nuxt/D4HTNcLU.js +0 -188
  63. package/dist/devtools-client/_nuxt/DU3BlAm5.js +0 -1
  64. package/dist/devtools-client/_nuxt/DiaY4J4_.js +0 -1
  65. package/dist/devtools-client/_nuxt/builds/meta/0b4ab733-07a6-40b2-b25e-aca95fa55188.json +0 -1
  66. package/dist/devtools-client/_nuxt/entry.XOvcedFq.css +0 -1
  67. package/dist/devtools-client/_nuxt/error-404.D2T48gBS.css +0 -1
  68. package/dist/devtools-client/_nuxt/error-500.sMTZJbsP.css +0 -1
  69. package/dist/devtools-client/_nuxt/index.DZD1lwyI.css +0 -1
@@ -115,12 +115,33 @@ onMounted(() => {
115
115
  defineExpose({
116
116
  player
117
117
  });
118
+ const keyboardTriggerable = computed(() => {
119
+ if (isTriggered.value)
120
+ return false;
121
+ const triggers = Array.isArray(props.trigger) ? props.trigger : [props.trigger];
122
+ return triggers.some((t) => typeof t === "string" && !["immediate", "onNuxtReady", "visibility", "visible"].includes(t));
123
+ });
124
+ function onPlaceholderKeydown(e) {
125
+ if (e.key !== "Enter" && e.key !== " ")
126
+ return;
127
+ e.preventDefault();
128
+ if (!rootEl.value)
129
+ return;
130
+ const triggers = (Array.isArray(props.trigger) ? props.trigger : [props.trigger]).filter(Boolean);
131
+ for (const t of triggers) {
132
+ if (["immediate", "onNuxtReady", "visibility", "visible"].includes(t))
133
+ continue;
134
+ rootEl.value.dispatchEvent(new Event(t, { bubbles: false }));
135
+ }
136
+ }
118
137
  const rootAttrs = computed(() => {
138
+ const interactive = keyboardTriggerable.value;
119
139
  return defu(props.rootAttrs, {
120
140
  "aria-busy": status.value === "loading",
121
- "aria-label": status.value === "awaitingLoad" ? "YouTube Player - Placeholder" : status.value === "loading" ? "YouTube Player - Loading" : "YouTube Player - Loaded",
141
+ "aria-label": status.value === "awaitingLoad" ? interactive ? "Play video" : "YouTube Player - Placeholder" : status.value === "loading" ? "YouTube Player - Loading" : "YouTube Player - Loaded",
122
142
  "aria-live": "polite",
123
- "role": "application",
143
+ "role": interactive ? "button" : "application",
144
+ "tabindex": interactive ? 0 : void 0,
124
145
  "style": {
125
146
  cursor: "pointer",
126
147
  position: "relative",
@@ -154,7 +175,7 @@ if (import.meta.server) {
154
175
  const placeholderAttrs = computed(() => {
155
176
  return defu(props.placeholderAttrs, {
156
177
  src: isFallbackPlaceHolder.value ? fallbackPlaceHolder.value : placeholder.value,
157
- alt: "",
178
+ alt: "Play video",
158
179
  loading: props.aboveTheFold ? "eager" : "lazy",
159
180
  // @ts-expect-error untyped
160
181
  fetchpriority: props.aboveTheFold ? "high" : void 0,
@@ -174,7 +195,7 @@ const placeholderAttrs = computed(() => {
174
195
  </script>
175
196
 
176
197
  <template>
177
- <div ref="rootEl" v-bind="rootAttrs">
198
+ <div ref="rootEl" v-bind="rootAttrs" @keydown="keyboardTriggerable ? onPlaceholderKeydown($event) : void 0">
178
199
  <div ref="youtubeEl" style="width: 100%; height: 100%; position: absolute; top: 0; left: 0;" />
179
200
  <slot v-if="!ready" :placeholder="placeholder" name="placeholder">
180
201
  <img v-bind="placeholderAttrs">
@@ -181,6 +181,7 @@ export function useScript(input, options) {
181
181
  if (!options.head) {
182
182
  throw new Error("useScript() has been called without Nuxt context.");
183
183
  }
184
+ const headHooks = options.head.hooks;
184
185
  ensureScripts(nuxtApp);
185
186
  const exists = !!nuxtApp.$scripts?.[id];
186
187
  const err = options._validate?.();
@@ -246,7 +247,7 @@ export function useScript(input, options) {
246
247
  ...ctx,
247
248
  trigger: typeof trigger === "object" ? trigger instanceof Promise ? "promise" : JSON.stringify(trigger) : trigger
248
249
  });
249
- options.head.hooks.hook("script:updated", (entry) => {
250
+ headHooks.hook("script:updated", (entry) => {
250
251
  if (entry.script.id !== instance.id)
251
252
  return;
252
253
  const status = entry.script.status;
@@ -292,7 +293,7 @@ export function useScript(input, options) {
292
293
  };
293
294
  nuxtApp._scripts = nuxtApp._scripts || {};
294
295
  if (!nuxtApp._scripts[instance.id]) {
295
- options.head.hooks.hook("script:updated", (ctx) => {
296
+ headHooks.hook("script:updated", (ctx) => {
296
297
  if (ctx.script.id !== instance.id)
297
298
  return;
298
299
  payload.events.push({
@@ -303,7 +304,7 @@ export function useScript(input, options) {
303
304
  payload.$script = instance;
304
305
  syncScripts();
305
306
  });
306
- options.head.hooks.hook("script:instance-fn", (ctx) => {
307
+ headHooks.hook("script:instance-fn", (ctx) => {
307
308
  if (ctx.script.id !== instance.id || String(ctx.fn).startsWith("__v_"))
308
309
  return;
309
310
  payload.events.push({
@@ -988,6 +988,75 @@ export declare const InitObjectPropertiesSchema: import("valibot").ObjectSchema<
988
988
  */
989
989
  readonly age: import("valibot").OptionalSchema<import("valibot").StringSchema<undefined>, undefined>;
990
990
  }, undefined>;
991
+ export declare const SpeedCurveOptions: import("valibot").ObjectSchema<{
992
+ /**
993
+ * Your SpeedCurve customer ID.
994
+ * @see https://support.speedcurve.com/docs/add-rum-to-your-site
995
+ */
996
+ readonly id: import("valibot").SchemaWithPipe<readonly [import("valibot").StringSchema<undefined>, import("valibot").MinLengthAction<string, 1, undefined>]>;
997
+ /**
998
+ * Enable SPA (single-page application) mode.
999
+ * When true, lux.js tracks soft navigations instead of full page loads.
1000
+ * @see https://support.speedcurve.com/docs/single-page-applications
1001
+ */
1002
+ readonly spaMode: import("valibot").OptionalSchema<import("valibot").BooleanSchema<undefined>, undefined>;
1003
+ /**
1004
+ * Automatically wire Vue Router hooks for SPA tracking when spaMode is true.
1005
+ * Set to false to instrument navigations manually.
1006
+ * @default true (when spaMode is true)
1007
+ */
1008
+ readonly autoTrackSpaNavigations: import("valibot").OptionalSchema<import("valibot").BooleanSchema<undefined>, undefined>;
1009
+ /**
1010
+ * Page label shown in the SpeedCurve dashboard.
1011
+ * Accepts a static string, a function `(to) => string | false` for per-navigation labels,
1012
+ * or `false` to disable labeling entirely. A callback returning `false` skips updating
1013
+ * the label for that navigation.
1014
+ * @default String(to.name ?? to.path)
1015
+ */
1016
+ readonly label: import("valibot").OptionalSchema<import("valibot").UnionSchema<[import("valibot").StringSchema<undefined>, import("valibot").FunctionSchema<undefined>, import("valibot").LiteralSchema<false, undefined>], undefined>, undefined>;
1017
+ /**
1018
+ * Sampling rate (0–100). Percentage of sessions that send beacons.
1019
+ * Upstream spelling is lowercase — matches LUX UserConfig.
1020
+ */
1021
+ readonly samplerate: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<undefined>, import("valibot").MinValueAction<number, 0, undefined>, import("valibot").MaxValueAction<number, 100, undefined>]>, undefined>;
1022
+ /**
1023
+ * Send the beacon when the page is hidden (pagehide event).
1024
+ * @default true
1025
+ */
1026
+ readonly sendBeaconOnPageHidden: import("valibot").OptionalSchema<import("valibot").BooleanSchema<undefined>, undefined>;
1027
+ /**
1028
+ * Track JavaScript errors.
1029
+ * @default true
1030
+ */
1031
+ readonly trackErrors: import("valibot").OptionalSchema<import("valibot").BooleanSchema<undefined>, undefined>;
1032
+ /**
1033
+ * Maximum number of errors to track per page view.
1034
+ * @default 5
1035
+ */
1036
+ readonly maxErrors: import("valibot").OptionalSchema<import("valibot").NumberSchema<undefined>, undefined>;
1037
+ /**
1038
+ * Minimum time (ms) before a beacon can be sent.
1039
+ */
1040
+ readonly minMeasureTime: import("valibot").OptionalSchema<import("valibot").NumberSchema<undefined>, undefined>;
1041
+ /**
1042
+ * Maximum time (ms) after which the beacon is sent regardless of load state.
1043
+ * @default 60000
1044
+ */
1045
+ readonly maxMeasureTime: import("valibot").OptionalSchema<import("valibot").NumberSchema<undefined>, undefined>;
1046
+ /**
1047
+ * Start a new beacon when the page becomes visible after being hidden.
1048
+ */
1049
+ readonly newBeaconOnPageShow: import("valibot").OptionalSchema<import("valibot").BooleanSchema<undefined>, undefined>;
1050
+ /**
1051
+ * Track pages loaded in background tabs.
1052
+ * @default false
1053
+ */
1054
+ readonly trackHiddenPages: import("valibot").OptionalSchema<import("valibot").BooleanSchema<undefined>, undefined>;
1055
+ /**
1056
+ * Cookie domain for cross-subdomain session tracking.
1057
+ */
1058
+ readonly cookieDomain: import("valibot").OptionalSchema<import("valibot").StringSchema<undefined>, undefined>;
1059
+ }, undefined>;
991
1060
  export declare const SnapTrPixelOptions: import("valibot").ObjectSchema<{
992
1061
  /**
993
1062
  * The user's email address (for matching).
@@ -1055,6 +1124,13 @@ export declare const StripeOptions: import("valibot").ObjectSchema<{
1055
1124
  * @see https://docs.stripe.com/disputes/prevention/advanced-fraud-detection
1056
1125
  */
1057
1126
  readonly advancedFraudSignals: import("valibot").OptionalSchema<import("valibot").BooleanSchema<undefined>, undefined>;
1127
+ /**
1128
+ * The Stripe.js SDK version to load.
1129
+ * Named releases get autocomplete; any future codename is also accepted.
1130
+ * @default 'basil'
1131
+ * @see https://docs.stripe.com/sdks/stripejs-versioning
1132
+ */
1133
+ readonly version: import("valibot").OptionalSchema<import("valibot").UnionSchema<[import("valibot").LiteralSchema<"v3", undefined>, import("valibot").LiteralSchema<"acacia", undefined>, import("valibot").LiteralSchema<"basil", undefined>, import("valibot").LiteralSchema<"clover", undefined>, import("valibot").LiteralSchema<"dahlia", undefined>, import("valibot").StringSchema<undefined>], undefined>, undefined>;
1058
1134
  }, undefined>;
1059
1135
  export declare const TikTokPixelOptions: import("valibot").ObjectSchema<{
1060
1136
  /**
@@ -1,4 +1,4 @@
1
- import { any, array, boolean, custom, literal, minLength, number, object, optional, pipe, record, string, union } from "valibot";
1
+ import { any, array, boolean, custom, function_, literal, maxValue, minLength, minValue, number, object, optional, pipe, record, string, union } from "valibot";
2
2
  const consentCategoryValue = union([literal("granted"), literal("denied")]);
3
3
  export const gcmConsentState = object({
4
4
  ad_storage: optional(consentCategoryValue),
@@ -947,6 +947,79 @@ export const InitObjectPropertiesSchema = object({
947
947
  */
948
948
  age: optional(string())
949
949
  });
950
+ export const SpeedCurveOptions = object({
951
+ // -- Composable-only options (consumed by useScriptSpeedCurve, not forwarded to LUX) --
952
+ /**
953
+ * Your SpeedCurve customer ID.
954
+ * @see https://support.speedcurve.com/docs/add-rum-to-your-site
955
+ */
956
+ id: pipe(string(), minLength(1)),
957
+ /**
958
+ * Enable SPA (single-page application) mode.
959
+ * When true, lux.js tracks soft navigations instead of full page loads.
960
+ * @see https://support.speedcurve.com/docs/single-page-applications
961
+ */
962
+ spaMode: optional(boolean()),
963
+ /**
964
+ * Automatically wire Vue Router hooks for SPA tracking when spaMode is true.
965
+ * Set to false to instrument navigations manually.
966
+ * @default true (when spaMode is true)
967
+ */
968
+ autoTrackSpaNavigations: optional(boolean()),
969
+ // -- LUX UserConfig passthrough (forwarded onto window.LUX before lux.js loads) --
970
+ // Property names match upstream LUX UserConfig casing exactly. Keep this list in
971
+ // sync with the LUX_USER_CONFIG_KEYS filter in speedcurve.ts.
972
+ /**
973
+ * Page label shown in the SpeedCurve dashboard.
974
+ * Accepts a static string, a function `(to) => string | false` for per-navigation labels,
975
+ * or `false` to disable labeling entirely. A callback returning `false` skips updating
976
+ * the label for that navigation.
977
+ * @default String(to.name ?? to.path)
978
+ */
979
+ label: optional(union([string(), function_(), literal(false)])),
980
+ /**
981
+ * Sampling rate (0–100). Percentage of sessions that send beacons.
982
+ * Upstream spelling is lowercase — matches LUX UserConfig.
983
+ */
984
+ samplerate: optional(pipe(number(), minValue(0), maxValue(100))),
985
+ /**
986
+ * Send the beacon when the page is hidden (pagehide event).
987
+ * @default true
988
+ */
989
+ sendBeaconOnPageHidden: optional(boolean()),
990
+ /**
991
+ * Track JavaScript errors.
992
+ * @default true
993
+ */
994
+ trackErrors: optional(boolean()),
995
+ /**
996
+ * Maximum number of errors to track per page view.
997
+ * @default 5
998
+ */
999
+ maxErrors: optional(number()),
1000
+ /**
1001
+ * Minimum time (ms) before a beacon can be sent.
1002
+ */
1003
+ minMeasureTime: optional(number()),
1004
+ /**
1005
+ * Maximum time (ms) after which the beacon is sent regardless of load state.
1006
+ * @default 60000
1007
+ */
1008
+ maxMeasureTime: optional(number()),
1009
+ /**
1010
+ * Start a new beacon when the page becomes visible after being hidden.
1011
+ */
1012
+ newBeaconOnPageShow: optional(boolean()),
1013
+ /**
1014
+ * Track pages loaded in background tabs.
1015
+ * @default false
1016
+ */
1017
+ trackHiddenPages: optional(boolean()),
1018
+ /**
1019
+ * Cookie domain for cross-subdomain session tracking.
1020
+ */
1021
+ cookieDomain: optional(string())
1022
+ });
950
1023
  export const SnapTrPixelOptions = object({
951
1024
  /**
952
1025
  * Your Snapchat Pixel ID.
@@ -966,7 +1039,14 @@ export const StripeOptions = object({
966
1039
  * @default true
967
1040
  * @see https://docs.stripe.com/disputes/prevention/advanced-fraud-detection
968
1041
  */
969
- advancedFraudSignals: optional(boolean())
1042
+ advancedFraudSignals: optional(boolean()),
1043
+ /**
1044
+ * The Stripe.js SDK version to load.
1045
+ * Named releases get autocomplete; any future codename is also accepted.
1046
+ * @default 'basil'
1047
+ * @see https://docs.stripe.com/sdks/stripejs-versioning
1048
+ */
1049
+ version: optional(union([literal("v3"), literal("acacia"), literal("basil"), literal("clover"), literal("dahlia"), string()]))
970
1050
  });
971
1051
  export const TikTokPixelOptions = object({
972
1052
  /**
@@ -0,0 +1,21 @@
1
+ import type { LuxGlobal, UserConfig } from '@speedcurve/lux';
2
+ import type { RouteLocationNormalized } from 'vue-router';
3
+ import type { RegistryScriptInput, UseScriptContext } from '#nuxt-scripts/types';
4
+ import { SpeedCurveOptions } from './schemas.js';
5
+ export { SpeedCurveOptions };
6
+ export interface SpeedCurveApi {
7
+ LUX: LuxGlobal;
8
+ }
9
+ declare global {
10
+ interface Window {
11
+ LUX?: LuxGlobal;
12
+ LUX_ae?: ErrorEvent[];
13
+ LUX_al?: PerformanceEntry[];
14
+ }
15
+ }
16
+ export type SpeedCurveInput = Omit<RegistryScriptInput<typeof SpeedCurveOptions>, 'label'> & Omit<UserConfig, 'label'> & {
17
+ label?: string | ((to: RouteLocationNormalized) => string | false) | false;
18
+ };
19
+ export declare function useScriptSpeedCurve<T extends SpeedCurveApi>(_options?: SpeedCurveInput): UseScriptContext<T>;
20
+ export declare function applyConfig(options: SpeedCurveInput): void;
21
+ export declare function installAutoTracker(options?: SpeedCurveInput): void;
@@ -0,0 +1,93 @@
1
+ import { useHead, useNuxtApp, useRouter } from "nuxt/app";
2
+ import { luxSnippetSource } from "#build/nuxt-scripts-speedcurve-snippet";
3
+ import { useRegistryScript } from "../utils.js";
4
+ import { afterNextPaint } from "../utils/after-next-paint.js";
5
+ import { SpeedCurveOptions } from "./schemas.js";
6
+ export { SpeedCurveOptions };
7
+ const LUX_USER_CONFIG_KEYS = Object.keys(SpeedCurveOptions.entries).filter(
8
+ (k) => k !== "id" && k !== "autoTrackSpaNavigations" && k !== "spaMode"
9
+ );
10
+ let luxWired = false;
11
+ export function useScriptSpeedCurve(_options) {
12
+ return useRegistryScript("speedcurve", (options) => ({
13
+ scriptInput: {
14
+ src: `https://cdn.speedcurve.com/js/lux.js?id=${options.id}`,
15
+ crossorigin: "anonymous"
16
+ },
17
+ schema: import.meta.dev ? SpeedCurveOptions : void 0,
18
+ scriptOptions: {
19
+ trigger: "client",
20
+ use: () => ({ LUX: window.LUX }),
21
+ beforeInit: () => {
22
+ useHead({
23
+ script: [{
24
+ key: "speedcurve-lux-primer",
25
+ tagPosition: "head",
26
+ tagPriority: "critical",
27
+ innerHTML: luxSnippetSource
28
+ }]
29
+ });
30
+ }
31
+ },
32
+ clientInit: import.meta.server ? void 0 : () => {
33
+ const input = options;
34
+ applyConfig(input);
35
+ if (options.spaMode && options.autoTrackSpaNavigations !== false)
36
+ installAutoTracker(input);
37
+ }
38
+ }), _options);
39
+ }
40
+ export function applyConfig(options) {
41
+ const lux = window.LUX;
42
+ if (!lux)
43
+ return;
44
+ for (const k of LUX_USER_CONFIG_KEYS) {
45
+ const v = options[k];
46
+ if (v === void 0)
47
+ continue;
48
+ if (k === "label" && typeof v !== "string")
49
+ continue;
50
+ lux[k] = v;
51
+ }
52
+ }
53
+ export function installAutoTracker(options) {
54
+ if (luxWired)
55
+ return;
56
+ luxWired = true;
57
+ const router = useRouter();
58
+ const nuxt = useNuxtApp();
59
+ let pendingInitial = nuxt.payload?.serverRendered === false;
60
+ const label = options?.label === false ? null : typeof options?.label === "function" ? options.label : (to) => String(to.name ?? to.path);
61
+ router.beforeEach((to) => {
62
+ const lux = window.LUX;
63
+ if (!lux)
64
+ return;
65
+ if (pendingInitial) {
66
+ pendingInitial = false;
67
+ if (label) {
68
+ const nextLabel = label(to);
69
+ if (nextLabel !== false)
70
+ lux.label = nextLabel;
71
+ }
72
+ return;
73
+ }
74
+ lux.startSoftNavigation();
75
+ if (label) {
76
+ const nextLabel = label(to);
77
+ if (nextLabel !== false)
78
+ lux.label = nextLabel;
79
+ }
80
+ });
81
+ router.afterEach((_to, _from, failure) => {
82
+ if (!failure)
83
+ return;
84
+ const lux = window.LUX;
85
+ if (!lux)
86
+ return;
87
+ lux.markLoadTime();
88
+ lux.addData("luxNavFailed", "1");
89
+ });
90
+ nuxt.hook("page:finish", () => {
91
+ afterNextPaint(() => window.LUX?.markLoadTime());
92
+ });
93
+ }
@@ -6,7 +6,7 @@ export function useScriptStripe(_options) {
6
6
  return useRegistryScript("stripe", (options) => ({
7
7
  scriptInput: {
8
8
  src: withQuery(
9
- `https://js.stripe.com/basil/stripe.js`,
9
+ `https://js.stripe.com/${options?.version || "basil"}/stripe.js`,
10
10
  typeof options?.advancedFraudSignals === "boolean" && !options?.advancedFraudSignals ? { advancedFraudSignals: false } : {}
11
11
  ),
12
12
  // opt-out of privacy defaults
@@ -47,22 +47,61 @@ interface TrackOptions {
47
47
  test_event_code?: string;
48
48
  [key: string]: any;
49
49
  }
50
- type TtqFns = ((cmd: 'track', event: StandardEvents | (string & {}), properties?: EventProperties, options?: TrackOptions) => void) & ((cmd: 'page') => void) & ((cmd: 'identify', properties: IdentifyProperties) => void) & ((cmd: (string & {}), ...args: any[]) => void);
50
+ /**
51
+ * The deferred methods TikTok's base code installs on `ttq`. Calling one before
52
+ * `events.js` loads pushes a `[method, ...args]` tuple onto the queue; afterwards
53
+ * `events.js` rebinds them to live implementations.
54
+ */
55
+ export interface TtqInstance {
56
+ /** Track a page view. */
57
+ page: () => void;
58
+ /** Track a standard or custom conversion event. */
59
+ track: (event: StandardEvents | (string & {}), properties?: EventProperties, options?: TrackOptions) => void;
60
+ /** Associate advanced-matching identifiers with the current user. */
61
+ identify: (properties: IdentifyProperties) => void;
62
+ /** Opt user in to tracking. Queued before the script loads; live once `events.js` binds. */
63
+ grantConsent: () => void;
64
+ /** Opt user out of tracking. Queued before the script loads; live once `events.js` binds. */
65
+ revokeConsent: () => void;
66
+ /** Defer consent until an explicit grant/revoke. Queued before the script loads; live once `events.js` binds. */
67
+ holdConsent: () => void;
68
+ enableCookie: () => void;
69
+ disableCookie: () => void;
70
+ }
71
+ /**
72
+ * Legacy callable signature. Pre-1.2 `useScriptTikTokPixel` exposed `ttq` as an
73
+ * `fbq`-style callable (`ttq('page')`, `ttq('track', …)`); the adapter keeps it
74
+ * working alongside the method form.
75
+ */
76
+ type TtqCallable = ((cmd: 'track', event: StandardEvents | (string & {}), properties?: EventProperties, options?: TrackOptions) => void) & ((cmd: 'page') => void) & ((cmd: 'identify', properties: IdentifyProperties) => void) & ((cmd: (string & {}), ...args: any[]) => void);
77
+ /**
78
+ * The public `ttq` returned by the composable: a callable adapter (legacy form)
79
+ * that also carries the deferred methods (`ttq.page()`, `ttq.track(…)`). The
80
+ * adapter forwards to `window.ttq`, which is TikTok's real array protocol.
81
+ */
82
+ export type Ttq = TtqCallable & TtqInstance & {
83
+ /** Resolve the per-pixel method bag for a specific pixel id. */
84
+ instance: (id: string) => TtqInstance;
85
+ };
51
86
  export interface TikTokPixelApi {
52
- ttq: TtqFns & {
53
- push: TtqFns;
54
- loaded: boolean;
55
- queue: any[];
56
- /** Opt user in to tracking. Queued before the script loads; live once `events.js` binds. */
57
- grantConsent: () => void;
58
- /** Opt user out of tracking. Queued before the script loads; live once `events.js` binds. */
59
- revokeConsent: () => void;
60
- /** Defer consent until an explicit grant/revoke. Queued before the script loads; live once `events.js` binds. */
61
- holdConsent: () => void;
62
- };
87
+ ttq: Ttq;
63
88
  }
89
+ /**
90
+ * TikTok's `window.ttq` is an Array, not a callable: `events.js` reads it as a
91
+ * queue of `[method, ...args]` tuples and replays them, with the deferred
92
+ * methods installed as own properties on the array.
93
+ */
94
+ type TtqArray = TtqInstance & Array<any[]> & {
95
+ methods: string[];
96
+ setAndDefer: (target: any, method: string) => void;
97
+ instance: (id: string) => TtqInstance;
98
+ _i?: Record<string, any[]>;
99
+ _t?: Record<string, number>;
100
+ _o?: Record<string, any>;
101
+ };
64
102
  declare global {
65
- interface Window extends TikTokPixelApi {
103
+ interface Window {
104
+ ttq: TtqArray;
66
105
  TiktokAnalyticsObject: string;
67
106
  }
68
107
  }
@@ -6,6 +6,7 @@ export { TikTokPixelOptions };
6
6
  export function tiktokPixelSrc(region) {
7
7
  return region === "us" ? "https://analytics.us.tiktok.com/i18n/pixel/events.js" : "https://analytics.tiktok.com/i18n/pixel/events.js";
8
8
  }
9
+ const TTQ_METHODS = ["page", "track", "identify", "instances", "debug", "on", "off", "once", "ready", "alias", "group", "enableCookie", "disableCookie", "holdConsent", "revokeConsent", "grantConsent"];
9
10
  const SHA256_HEX = /^[a-f0-9]{64}$/i;
10
11
  function warnUnhashedIdentify(props) {
11
12
  const hashFields = ["email", "phone_number", "external_id", "first_name", "last_name", "city", "state", "country", "zip_code"];
@@ -17,6 +18,17 @@ function warnUnhashedIdentify(props) {
17
18
  logger.withTag("tiktokPixel").warn(`identify() received unhashed value(s) for ${offenders.join(", ")}. TikTok requires SHA-256 hashing for advanced matching; raw values will be ignored. See https://business-api.tiktok.com/portal/docs?id=1739585702922241`);
18
19
  }
19
20
  }
21
+ function createTtqAdapter() {
22
+ const dispatch = (method, ...args) => {
23
+ if (import.meta.dev && method === "identify" && args[0])
24
+ warnUnhashedIdentify(args[0]);
25
+ return window.ttq[method]?.(...args);
26
+ };
27
+ const adapter = ((method, ...args) => dispatch(method, ...args));
28
+ for (const method of [...TTQ_METHODS, "instance"])
29
+ adapter[method] = (...args) => dispatch(method, ...args);
30
+ return adapter;
31
+ }
20
32
  export function useScriptTikTokPixel(_options) {
21
33
  const instance = useRegistryScript("tiktokPixel", (options) => ({
22
34
  scriptInput: {
@@ -29,41 +41,46 @@ export function useScriptTikTokPixel(_options) {
29
41
  schema: import.meta.dev ? TikTokPixelOptions : void 0,
30
42
  scriptOptions: {
31
43
  use() {
32
- return { ttq: window.ttq };
44
+ return { ttq: createTtqAdapter() };
33
45
  }
34
46
  },
47
+ // TikTok's `events.js` consumes the official array-based snippet protocol:
48
+ // `window.ttq` is an Array of `[method, ...args]` tuples that `events.js`
49
+ // drains once loaded. (It does NOT understand the Facebook `fbq` callable
50
+ // protocol — using that shape silently drops every browser Pixel event.)
35
51
  clientInit: import.meta.server ? void 0 : () => {
36
52
  window.TiktokAnalyticsObject = "ttq";
37
- const ttq = window.ttq = function(...params) {
38
- if (import.meta.dev && params[0] === "identify" && params[1])
39
- warnUnhashedIdentify(params[1]);
40
- if (ttq.callMethod) {
41
- ttq.callMethod(...params);
42
- } else {
43
- ttq.queue.push(params);
44
- }
45
- };
46
- ttq.push = ttq;
47
- ttq.loaded = true;
48
- ttq.queue = [];
49
- const consentMethods = ["grantConsent", "revokeConsent", "holdConsent"];
50
- for (const name of consentMethods) {
51
- ;
52
- ttq[name] = function(...params) {
53
- ttq.queue.push([name, ...params]);
53
+ const ttq = window.ttq = window.ttq || [];
54
+ ttq.methods = TTQ_METHODS;
55
+ ttq.setAndDefer = function(target, method) {
56
+ target[method] = function(...args) {
57
+ target.push([method, ...args]);
54
58
  };
55
- }
56
- if (options?.defaultConsent === "granted")
57
- ttq.grantConsent();
58
- else if (options?.defaultConsent === "denied")
59
- ttq.revokeConsent();
60
- else if (options?.defaultConsent === "hold")
61
- ttq.holdConsent();
59
+ };
60
+ for (const method of ttq.methods)
61
+ ttq.setAndDefer(ttq, method);
62
+ ttq.instance = function(id) {
63
+ const bag = ttq._i?.[id] || [];
64
+ for (const method of ttq.methods)
65
+ ttq.setAndDefer(bag, method);
66
+ return bag;
67
+ };
62
68
  if (options?.id) {
63
- ttq("init", options.id);
64
- if (options?.trackPageView !== false) {
65
- ttq("page");
66
- }
69
+ ttq._i = ttq._i || {};
70
+ ttq._i[options.id] = ttq._i[options.id] || [];
71
+ ttq._i[options.id]._u = tiktokPixelSrc(options?.region);
72
+ ttq._t = ttq._t || {};
73
+ ttq._t[options.id] = Date.now();
74
+ ttq._o = ttq._o || {};
75
+ ttq._o[options.id] = {};
76
+ if (options?.defaultConsent === "granted")
77
+ ttq.grantConsent();
78
+ else if (options?.defaultConsent === "denied")
79
+ ttq.revokeConsent();
80
+ else if (options?.defaultConsent === "hold")
81
+ ttq.holdConsent();
82
+ if (options?.trackPageView !== false)
83
+ ttq.page();
67
84
  }
68
85
  }
69
86
  }), _options);
@@ -1,16 +1,41 @@
1
1
  import { createError, defineEventHandler, getQuery, setHeader } from "h3";
2
- import { useRuntimeConfig } from "nitropack/runtime";
2
+ import { defineCachedFunction, useRuntimeConfig } from "nitropack/runtime";
3
+ import { $fetch } from "ofetch";
3
4
  import { ELEMENT_NODE, parse, renderSync, TEXT_NODE, walkSync } from "ultrahtml";
4
5
  import { createCachedJsonFetch } from "./utils/cached-upstream.js";
5
- import { proxyAssetUrl, rewriteUrl, rewriteUrlsInText, RSRC_RE, scopeCss } from "./utils/instagram-embed.js";
6
+ import { isEmbedShell, proxyAssetUrl, rewriteUrl, rewriteUrlsInText, RSRC_RE, scopeCss } from "./utils/instagram-embed.js";
6
7
  import { withSigning } from "./utils/withSigning.js";
7
8
  export { proxyAssetUrl, proxyImageUrl, rewriteUrl, rewriteUrlsInText, scopeCss } from "./utils/instagram-embed.js";
8
9
  const EMBED_INSTAGRAM_SUFFIX_RE = /\/embed\/instagram$/;
9
10
  const SRCSET_SPLIT_RE = /\s+/;
10
- const cachedEmbedFetch = createCachedJsonFetch(
11
- "nuxt-scripts-instagram-embed",
12
- 600,
13
- (url) => url
11
+ const cachedEmbedFetch = defineCachedFunction(
12
+ async (url, headers) => {
13
+ const html = await $fetch(url, { timeout: 1e4, headers });
14
+ if (isEmbedShell(html)) {
15
+ throw createError({
16
+ statusCode: 502,
17
+ statusMessage: "Instagram returned an empty embed shell (post unavailable or upstream rate-limiting)"
18
+ });
19
+ }
20
+ return html;
21
+ },
22
+ {
23
+ // v2 — bump to evict any v1 entries that cached the empty JS shell
24
+ // before the shell-detection / UA fix landed.
25
+ name: "nuxt-scripts-instagram-embed-v2",
26
+ maxAge: 600,
27
+ swr: true,
28
+ staleMaxAge: 600,
29
+ // Vary on headers too — Instagram's response is UA-dependent, so
30
+ // different callers (e.g. unit tests, future UA changes) must not
31
+ // collide on the same key.
32
+ getKey: (url, headers) => {
33
+ const parts = [url];
34
+ for (const [k, v] of Object.entries(headers).sort(([a], [b]) => a.localeCompare(b)))
35
+ parts.push(`${k}=${v}`);
36
+ return parts.join("|");
37
+ }
38
+ }
14
39
  );
15
40
  const cachedCssFetch = createCachedJsonFetch(
16
41
  "nuxt-scripts-instagram-css",
@@ -56,10 +81,12 @@ export default withSigning(defineEventHandler(async (event) => {
56
81
  const cleanUrl = parsedUrl.origin + pathname;
57
82
  const embedUrl = `${cleanUrl}embed/${captions ? "captioned/" : ""}`;
58
83
  const html = await cachedEmbedFetch(embedUrl, {
59
- headers: {
60
- "Accept": "text/html",
61
- "User-Agent": "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
62
- }
84
+ "Accept": "text/html",
85
+ // Meta's own crawler UA. Googlebot's UA is also accepted by Instagram
86
+ // but is IP-verified, so it fails from hosts outside Google's ranges
87
+ // (e.g. Cloudflare/Vercel) and Instagram serves the JS shell instead
88
+ // of the SSR'd post.
89
+ "User-Agent": "facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)"
63
90
  }).catch((error) => {
64
91
  throw createError({
65
92
  statusCode: error.statusCode || 500,
@@ -1,3 +1,4 @@
1
+ export declare const SKIP_REQUEST_HEADERS: Set<string>;
1
2
  /**
2
3
  * Privacy-aware proxy handler for first-party script collection endpoints.
3
4
  * Routes requests to third-party analytics while protecting user privacy.