@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.
- package/dist/devtools-client/200.html +1 -1
- package/dist/devtools-client/404.html +1 -1
- package/dist/devtools-client/_nuxt/BTXw0v4h.js +1 -0
- package/dist/devtools-client/_nuxt/Bf6N2ObH.js +1 -0
- package/dist/devtools-client/_nuxt/C5Bq9Qgz.js +1 -0
- package/dist/devtools-client/_nuxt/CSw0kWhI.js +1 -0
- package/dist/devtools-client/_nuxt/DWvCkA0U.js +195 -0
- package/dist/devtools-client/_nuxt/TPgAGQc8.js +1 -0
- package/dist/devtools-client/_nuxt/builds/latest.json +1 -1
- package/dist/devtools-client/_nuxt/builds/meta/df290ad6-a770-4973-bb7f-110a630b368c.json +1 -0
- package/dist/devtools-client/_nuxt/dvDf1_6X.js +1 -0
- package/dist/devtools-client/_nuxt/entry.VwiedW44.css +1 -0
- package/dist/devtools-client/_nuxt/error-404.DBJH6QEN.css +1 -0
- package/dist/devtools-client/_nuxt/error-500.BIIPjeaA.css +1 -0
- package/dist/devtools-client/_nuxt/{first-party.C8Ha4JLM.css → first-party.D_SBdnka.css} +1 -1
- package/dist/devtools-client/_nuxt/index.DIj4bh55.css +1 -0
- package/dist/devtools-client/_nuxt/{registry.B9lnjF_b.css → registry.CDbTq1x3.css} +1 -1
- package/dist/devtools-client/docs/index.html +1 -1
- package/dist/devtools-client/first-party/index.html +1 -1
- package/dist/devtools-client/index.html +1 -1
- package/dist/devtools-client/registry/index.html +1 -1
- package/dist/module.d.mts +5 -1
- package/dist/module.d.ts +5 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +35 -4
- package/dist/registry.mjs +16 -3
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsHeatmapLayer.d.vue.ts +18 -1
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsHeatmapLayer.vue +7 -1
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsHeatmapLayer.vue.d.ts +18 -1
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.d.vue.ts +9 -0
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.vue +6 -5
- package/dist/runtime/components/GoogleMaps/ScriptGoogleMapsStaticMap.vue.d.ts +9 -0
- package/dist/runtime/components/ScriptGravatar.vue +2 -0
- package/dist/runtime/components/ScriptLoadingIndicator.vue +1 -1
- package/dist/runtime/components/ScriptVimeoPlayer.vue +27 -4
- package/dist/runtime/components/ScriptYouTubePlayer.vue +25 -4
- package/dist/runtime/composables/useScript.js +4 -3
- package/dist/runtime/registry/schemas.d.ts +76 -0
- package/dist/runtime/registry/schemas.js +82 -2
- package/dist/runtime/registry/speedcurve.d.ts +21 -0
- package/dist/runtime/registry/speedcurve.js +93 -0
- package/dist/runtime/registry/stripe.js +1 -1
- package/dist/runtime/registry/tiktok-pixel.d.ts +52 -13
- package/dist/runtime/registry/tiktok-pixel.js +46 -29
- package/dist/runtime/server/instagram-embed.js +37 -10
- package/dist/runtime/server/proxy-handler.d.ts +1 -0
- package/dist/runtime/server/proxy-handler.js +16 -0
- package/dist/runtime/server/utils/instagram-embed.d.ts +1 -0
- package/dist/runtime/server/utils/instagram-embed.js +5 -0
- package/dist/runtime/types.d.ts +3 -1
- package/dist/runtime/utils/after-next-paint.d.ts +1 -0
- package/dist/runtime/utils/after-next-paint.js +3 -0
- package/dist/stats.d.mts +1 -1
- package/dist/stats.d.ts +1 -1
- package/dist/stats.mjs +5 -0
- package/dist/types-source.mjs +127 -6
- package/package.json +12 -7
- package/dist/devtools-client/_nuxt/1wb58MKb.js +0 -1
- package/dist/devtools-client/_nuxt/BbLmrp_o.js +0 -1
- package/dist/devtools-client/_nuxt/Cx46cS8a.js +0 -1
- package/dist/devtools-client/_nuxt/D-M51CV3.js +0 -1
- package/dist/devtools-client/_nuxt/D4HTNcLU.js +0 -188
- package/dist/devtools-client/_nuxt/DU3BlAm5.js +0 -1
- package/dist/devtools-client/_nuxt/DiaY4J4_.js +0 -1
- package/dist/devtools-client/_nuxt/builds/meta/0b4ab733-07a6-40b2-b25e-aca95fa55188.json +0 -1
- package/dist/devtools-client/_nuxt/entry.XOvcedFq.css +0 -1
- package/dist/devtools-client/_nuxt/error-404.D2T48gBS.css +0 -1
- package/dist/devtools-client/_nuxt/error-500.sMTZJbsP.css +0 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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 =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
57
|
-
ttq.
|
|
58
|
-
|
|
59
|
-
ttq.
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
64
|
-
|
|
65
|
-
|
|
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 =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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,
|