@nuxt/scripts 1.3.3 → 1.3.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.
Files changed (96) 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/{DAubxma0.js → 8ltZ0o6d.js} +1 -1
  4. package/dist/devtools-client/_nuxt/{CBuBrHyL.js → B8_EBdhg.js} +1 -1
  5. package/dist/devtools-client/_nuxt/CC0GKROJ.js +1 -0
  6. package/dist/devtools-client/_nuxt/{DvC3yV2P.js → CMXzQxJZ.js} +1 -1
  7. package/dist/devtools-client/_nuxt/{xAZ2vgo3.js → CP7c-Js6.js} +4 -4
  8. package/dist/devtools-client/_nuxt/{DzbSCA_w.js → DTP8MRtW.js} +1 -1
  9. package/dist/devtools-client/_nuxt/{DgAtAWdT.js → Dkc7iTdR.js} +1 -1
  10. package/dist/devtools-client/_nuxt/Dl7W1ViV.js +28 -0
  11. package/dist/devtools-client/_nuxt/{WOtyQdps.js → KxC4Q3ev.js} +1 -1
  12. package/dist/devtools-client/_nuxt/builds/latest.json +1 -1
  13. package/dist/devtools-client/_nuxt/builds/meta/c38edf4f-387c-4148-a34a-ca78a5b40869.json +1 -0
  14. package/dist/devtools-client/_nuxt/error-404.Bq2JQbJs.css +1 -0
  15. package/dist/devtools-client/_nuxt/error-500.5E_FOzlc.css +1 -0
  16. package/dist/devtools-client/_nuxt/{pages.BF1VnccH.css → pages.aU4WirOM.css} +1 -1
  17. package/dist/devtools-client/docs/index.html +1 -1
  18. package/dist/devtools-client/first-party/index.html +1 -1
  19. package/dist/devtools-client/index.html +1 -1
  20. package/dist/devtools-client/registry/index.html +1 -1
  21. package/dist/module.d.mts +1 -1
  22. package/dist/module.d.ts +1 -1
  23. package/dist/module.json +1 -1
  24. package/dist/module.mjs +263 -47
  25. package/dist/registry.mjs +2 -2
  26. package/dist/runtime/components/GoogleMaps/ScriptGoogleMaps.vue +71 -21
  27. package/dist/runtime/components/GoogleMaps/useGoogleMapsResource.d.ts +2 -1
  28. package/dist/runtime/components/GoogleMaps/useGoogleMapsResource.js +39 -25
  29. package/dist/runtime/components/ScriptCarbonAds.vue +11 -3
  30. package/dist/runtime/components/ScriptCrisp.vue +4 -1
  31. package/dist/runtime/components/ScriptIntercom.vue +4 -1
  32. package/dist/runtime/components/ScriptLemonSqueezy.d.vue.ts +13 -13
  33. package/dist/runtime/components/ScriptLemonSqueezy.vue +30 -6
  34. package/dist/runtime/components/ScriptLemonSqueezy.vue.d.ts +13 -13
  35. package/dist/runtime/components/ScriptPayPalButtons.vue +8 -1
  36. package/dist/runtime/components/ScriptPayPalMessages.vue +6 -0
  37. package/dist/runtime/components/ScriptStripePricingTable.vue +2 -0
  38. package/dist/runtime/components/ScriptVimeoPlayer.vue +8 -1
  39. package/dist/runtime/composables/useScript.js +121 -44
  40. package/dist/runtime/composables/useScriptEventPage.js +36 -20
  41. package/dist/runtime/composables/useScriptTriggerIdleTimeout.js +19 -5
  42. package/dist/runtime/composables/useScriptTriggerInteraction.js +23 -10
  43. package/dist/runtime/composables/useScriptTriggerServiceWorker.js +21 -18
  44. package/dist/runtime/devtools-standalone-bridge.client.js +28 -3
  45. package/dist/runtime/npm-script-proxy.d.ts +7 -0
  46. package/dist/runtime/npm-script-proxy.js +46 -0
  47. package/dist/runtime/npm-script-stub.js +4 -5
  48. package/dist/runtime/registry/posthog.js +11 -3
  49. package/dist/runtime/registry/speedcurve.js +33 -4
  50. package/dist/runtime/registry/usercentrics.js +4 -21
  51. package/dist/runtime/registry/youtube-player.js +16 -5
  52. package/dist/runtime/server/bluesky-embed.js +15 -6
  53. package/dist/runtime/server/google-maps-geocode-proxy.js +10 -5
  54. package/dist/runtime/server/google-static-maps-proxy.js +20 -13
  55. package/dist/runtime/server/gravatar-proxy.js +48 -11
  56. package/dist/runtime/server/instagram-embed-asset.js +1 -1
  57. package/dist/runtime/server/instagram-embed.js +44 -94
  58. package/dist/runtime/server/proxy-handler.d.ts +2 -1
  59. package/dist/runtime/server/proxy-handler.js +183 -23
  60. package/dist/runtime/server/utils/cache-config.d.ts +14 -0
  61. package/dist/runtime/server/utils/cache-config.js +13 -0
  62. package/dist/runtime/server/utils/cached-upstream.d.ts +23 -2
  63. package/dist/runtime/server/utils/cached-upstream.js +212 -15
  64. package/dist/runtime/server/utils/image-proxy.d.ts +3 -2
  65. package/dist/runtime/server/utils/image-proxy.js +26 -16
  66. package/dist/runtime/server/utils/instagram-embed.d.ts +8 -0
  67. package/dist/runtime/server/utils/instagram-embed.js +155 -0
  68. package/dist/runtime/server/utils/network-host.d.ts +21 -0
  69. package/dist/runtime/server/utils/network-host.js +112 -0
  70. package/dist/runtime/server/utils/privacy.js +55 -19
  71. package/dist/runtime/server/utils/proxy-query.d.ts +2 -0
  72. package/dist/runtime/server/utils/proxy-query.js +10 -0
  73. package/dist/runtime/server/utils/sign.d.ts +1 -1
  74. package/dist/runtime/server/utils/sign.js +1 -1
  75. package/dist/runtime/server/utils/withSigning.d.ts +1 -1
  76. package/dist/runtime/server/utils/withSigning.js +3 -3
  77. package/dist/runtime/server/vercel-insights-sink.js +1 -1
  78. package/dist/runtime/server/x-embed.js +8 -4
  79. package/dist/runtime/unhead-features.d.ts +1 -0
  80. package/dist/runtime/unhead-features.js +3 -0
  81. package/dist/runtime/utils/abortable-promise.d.ts +8 -0
  82. package/dist/runtime/utils/abortable-promise.js +63 -0
  83. package/dist/runtime/utils/after-next-paint.d.ts +1 -1
  84. package/dist/runtime/utils/after-next-paint.js +16 -1
  85. package/dist/runtime/utils/usercentrics-consent.d.ts +10 -0
  86. package/dist/runtime/utils/usercentrics-consent.js +88 -0
  87. package/dist/runtime/utils/youtube-readiness.d.ts +7 -0
  88. package/dist/runtime/utils/youtube-readiness.js +45 -0
  89. package/dist/runtime/utils.d.ts +3 -1
  90. package/dist/runtime/utils.js +29 -0
  91. package/package.json +2 -1
  92. package/dist/devtools-client/_nuxt/BGGk5RYW.js +0 -1
  93. package/dist/devtools-client/_nuxt/C8xLTMBy.js +0 -28
  94. package/dist/devtools-client/_nuxt/builds/meta/0c193d7f-fdc4-4ec6-b4c0-7ba4cc94709b.json +0 -1
  95. package/dist/devtools-client/_nuxt/error-404.CIn5oEzb.css +0 -1
  96. package/dist/devtools-client/_nuxt/error-500.B1YZl8g8.css +0 -1
@@ -1,10 +1,17 @@
1
1
  import { useScript as _useScript } from "@unhead/vue/scripts";
2
2
  import { defu } from "defu";
3
3
  import { injectHead, onNuxtReady, useHead, useNuxtApp, useRuntimeConfig } from "nuxt/app";
4
- import { markRaw, ref } from "vue";
4
+ import { markRaw, ref, watch } from "vue";
5
5
  import { resolveTrigger } from "#build/nuxt-scripts-trigger-resolver";
6
6
  import { debugEnabled } from "../debug.js";
7
7
  import { logger } from "../logger.js";
8
+ const DEVTOOLS_EVENT_LIMIT = 250;
9
+ const DEVTOOLS_NETWORK_REQUEST_LIMIT = 500;
10
+ function pushBounded(items, item, limit) {
11
+ items.push(item);
12
+ if (items.length > limit)
13
+ items.splice(0, items.length - limit);
14
+ }
8
15
  function resolveProxyPrefix() {
9
16
  const devtoolsConfig = useRuntimeConfig().public["nuxt-scripts-devtools"];
10
17
  return devtoolsConfig?.proxyPrefix || "/_scripts/p";
@@ -67,6 +74,7 @@ function observeNetworkRequests(payload, domains, onUpdate, scriptSrc) {
67
74
  const proxyPrefix = resolveProxyPrefix();
68
75
  const matchesScript = createDomainMatcher(domains, proxyPrefix, scriptSrc);
69
76
  const seen = /* @__PURE__ */ new Set();
77
+ const seenOrder = [];
70
78
  function entryKey(entry) {
71
79
  return `${entry.name}@${entry.startTime}`;
72
80
  }
@@ -77,7 +85,13 @@ function observeNetworkRequests(payload, domains, onUpdate, scriptSrc) {
77
85
  if (!matchesScript(entry))
78
86
  return false;
79
87
  seen.add(key);
80
- payload.networkRequests.push(toNetworkRequest(entry, proxyPrefix));
88
+ seenOrder.push(key);
89
+ if (seenOrder.length > DEVTOOLS_NETWORK_REQUEST_LIMIT) {
90
+ const oldest = seenOrder.shift();
91
+ if (oldest)
92
+ seen.delete(oldest);
93
+ }
94
+ pushBounded(payload.networkRequests, toNetworkRequest(entry, proxyPrefix), DEVTOOLS_NETWORK_REQUEST_LIMIT);
81
95
  return true;
82
96
  }
83
97
  for (const entry of performance.getEntriesByType("resource"))
@@ -131,15 +145,35 @@ export function useScript(input, options) {
131
145
  });
132
146
  const nuxtApp2 = useNuxtApp();
133
147
  ensureScripts(nuxtApp2);
148
+ const existing = nuxtApp2.$scripts[src];
149
+ if (existing)
150
+ return existing;
134
151
  const status = ref("loaded");
135
152
  let disconnectObserver = () => {
136
153
  };
137
- const stub = {
154
+ let stopAppUnmountHook = () => {
155
+ };
156
+ let cleaned2 = false;
157
+ let stub;
158
+ const cleanup = () => {
159
+ if (cleaned2)
160
+ return;
161
+ cleaned2 = true;
162
+ disconnectObserver();
163
+ stopAppUnmountHook();
164
+ if (nuxtApp2.$scripts[src] === stub)
165
+ delete nuxtApp2.$scripts[src];
166
+ if (import.meta.dev && import.meta.client && nuxtApp2._scripts?.[src]) {
167
+ delete nuxtApp2._scripts[src];
168
+ nuxtApp2.hooks.callHook("scripts:updated", { scripts: nuxtApp2._scripts });
169
+ }
170
+ };
171
+ stub = {
138
172
  id: src,
139
173
  status,
140
174
  load: () => Promise.resolve({}),
141
175
  remove: () => {
142
- disconnectObserver();
176
+ cleanup();
143
177
  return false;
144
178
  },
145
179
  entry: void 0
@@ -147,6 +181,8 @@ export function useScript(input, options) {
147
181
  nuxtApp2.$scripts[src] = stub;
148
182
  if (import.meta.dev && import.meta.client) {
149
183
  let syncScripts = function() {
184
+ if (cleaned2)
185
+ return;
150
186
  nuxtApp2._scripts[src] = payload;
151
187
  nuxtApp2.hooks.callHook("scripts:updated", { scripts: nuxtApp2._scripts });
152
188
  };
@@ -168,6 +204,7 @@ export function useScript(input, options) {
168
204
  disconnectObserver = observeNetworkRequests(payload, domains, syncScripts, src);
169
205
  syncScripts();
170
206
  }
207
+ stopAppUnmountHook = nuxtApp2.hooks.hook("app:unmount", cleanup);
171
208
  return stub;
172
209
  }
173
210
  if (import.meta.dev && options.bundle === "unsupported") {
@@ -210,31 +247,75 @@ export function useScript(input, options) {
210
247
  options.trigger = onNuxtReady;
211
248
  }
212
249
  }
213
- const instance = _useScript(input, options);
214
- const _remove = instance.remove;
250
+ const unheadOptions = { ...options };
251
+ const instance = _useScript(input, unheadOptions);
252
+ delete unheadOptions.eventContext;
253
+ if (exists)
254
+ return instance;
255
+ markRaw(instance);
256
+ instance.toJSON = () => ({ id: instance.id, status: instance.status.value });
257
+ const cleanupFns = /* @__PURE__ */ new Set();
258
+ let cleaned = false;
259
+ const publicStatus = instance.status;
260
+ let stopStatusSync = () => {
261
+ };
262
+ const addCleanup = (cleanup) => {
263
+ if (cleaned)
264
+ cleanup();
265
+ else
266
+ cleanupFns.add(cleanup);
267
+ };
268
+ const cleanupInstance = () => {
269
+ if (cleaned)
270
+ return;
271
+ cleaned = true;
272
+ for (const cleanup of cleanupFns)
273
+ cleanup();
274
+ cleanupFns.clear();
275
+ if (nuxtApp.$scripts[id] === instance)
276
+ delete nuxtApp.$scripts[id];
277
+ if (import.meta.dev && import.meta.client && nuxtApp._scripts?.[instance.id]) {
278
+ delete nuxtApp._scripts[instance.id];
279
+ nuxtApp.hooks.callHook("scripts:updated", { scripts: nuxtApp._scripts });
280
+ }
281
+ };
282
+ let currentRemove = instance.remove;
283
+ let currentLoad = instance.load;
284
+ addCleanup(() => stopStatusSync());
215
285
  instance.remove = () => {
216
- nuxtApp.$scripts[id] = void 0;
217
- return _remove();
286
+ const result = currentRemove();
287
+ cleanupInstance();
288
+ return result;
218
289
  };
219
- const _load = instance.load;
220
290
  instance.load = async () => {
221
291
  if (err) {
222
292
  return Promise.reject(err);
223
293
  }
224
- return _load();
294
+ return currentLoad();
225
295
  };
226
296
  instance.reload = async () => {
227
- instance.remove();
297
+ if (err)
298
+ return Promise.reject(err);
299
+ currentRemove();
228
300
  const reloadInput = typeof input === "string" ? { src: input, key: `${id}-${Date.now()}` } : { ...input, key: `${id}-${Date.now()}` };
229
- const reloaded = _useScript(reloadInput, { ...options, trigger: "client" });
230
- Object.assign(instance, {
231
- status: reloaded.status,
232
- entry: reloaded.entry
301
+ const reloadOptions = { ...options, trigger: "client" };
302
+ const reloaded = _useScript(reloadInput, reloadOptions);
303
+ delete reloadOptions.eventContext;
304
+ currentRemove = reloaded.remove;
305
+ currentLoad = reloaded.load;
306
+ stopStatusSync();
307
+ stopStatusSync = watch(reloaded.status, (status) => publicStatus.value = status, {
308
+ flush: "sync",
309
+ immediate: true
233
310
  });
234
- return reloaded.load();
311
+ instance.entry = reloaded.entry;
312
+ return currentLoad();
235
313
  };
236
314
  nuxtApp.$scripts[id] = instance;
237
- if (import.meta.client && debugEnabled) {
315
+ addCleanup(nuxtApp.hooks.hook("app:unmount", () => {
316
+ instance.remove();
317
+ }));
318
+ if (import.meta.client && debugEnabled && !exists) {
238
319
  const registryKey = options?.devtools?.registryKey;
239
320
  const src = input?.src;
240
321
  const trigger = options?.trigger;
@@ -252,7 +333,7 @@ export function useScript(input, options) {
252
333
  ...ctx,
253
334
  trigger: typeof trigger === "object" ? trigger instanceof Promise ? "promise" : JSON.stringify(trigger) : trigger
254
335
  });
255
- headHooks.hook("script:updated", (entry) => {
336
+ addCleanup(headHooks.hook("script:updated", (entry) => {
256
337
  if (entry.script.id !== instance.id)
257
338
  return;
258
339
  const status = entry.script.status;
@@ -264,7 +345,7 @@ export function useScript(input, options) {
264
345
  payload.loadMs = Math.round(performance.now() - tLoadStart);
265
346
  const fn = status === "error" ? log.warn : log.debug;
266
347
  fn(`status: ${status}`, payload);
267
- });
348
+ }));
268
349
  const _origLoad = instance.load;
269
350
  instance.load = () => {
270
351
  log.debug("load() called", ctx);
@@ -283,12 +364,11 @@ export function useScript(input, options) {
283
364
  }
284
365
  if (import.meta.dev && import.meta.client) {
285
366
  let syncScripts = function() {
367
+ if (cleaned)
368
+ return;
286
369
  nuxtApp._scripts[instance.id] = payload;
287
370
  nuxtApp.hooks.callHook("scripts:updated", { scripts: nuxtApp._scripts });
288
371
  };
289
- if (exists) {
290
- return instance;
291
- }
292
372
  const payload = {
293
373
  ...options.devtools,
294
374
  src: input.src,
@@ -298,64 +378,61 @@ export function useScript(input, options) {
298
378
  };
299
379
  nuxtApp._scripts = nuxtApp._scripts || {};
300
380
  if (!nuxtApp._scripts[instance.id]) {
301
- headHooks.hook("script:updated", (ctx) => {
381
+ addCleanup(headHooks.hook("script:updated", (ctx) => {
302
382
  if (ctx.script.id !== instance.id)
303
383
  return;
304
- payload.events.push({
384
+ pushBounded(payload.events, {
305
385
  type: "status",
306
386
  status: ctx.script.status,
307
387
  at: Date.now()
308
- });
388
+ }, DEVTOOLS_EVENT_LIMIT);
309
389
  payload.$script = instance;
310
390
  syncScripts();
311
- });
312
- headHooks.hook("script:instance-fn", (ctx) => {
391
+ }));
392
+ addCleanup(headHooks.hook("script:instance-fn", (ctx) => {
313
393
  if (ctx.script.id !== instance.id || String(ctx.fn).startsWith("__v_"))
314
394
  return;
315
- payload.events.push({
395
+ pushBounded(payload.events, {
316
396
  type: "fn-call",
317
397
  fn: ctx.fn,
318
398
  at: Date.now()
319
- });
399
+ }, DEVTOOLS_EVENT_LIMIT);
320
400
  syncScripts();
321
- });
401
+ }));
322
402
  payload.$script = instance;
323
403
  if (err) {
324
- payload.events.push({
404
+ pushBounded(payload.events, {
325
405
  type: "status",
326
406
  status: "validation-failed",
327
407
  args: err,
328
408
  at: Date.now()
329
- });
409
+ }, DEVTOOLS_EVENT_LIMIT);
330
410
  }
331
- payload.events.push({
411
+ pushBounded(payload.events, {
332
412
  type: "status",
333
413
  status: "awaitingLoad",
334
414
  trigger: options?.trigger,
335
415
  at: Date.now()
336
- });
416
+ }, DEVTOOLS_EVENT_LIMIT);
337
417
  const scriptHostname = extractExternalHostname(input.src);
338
418
  const domains = /* @__PURE__ */ new Set([
339
419
  ...scriptHostname ? [scriptHostname] : [],
340
420
  ...options.devtools?.domains || []
341
421
  ]);
342
422
  let disconnectObserver = observeNetworkRequests(payload, domains, syncScripts, input.src);
343
- const _origRemove = instance.remove;
423
+ addCleanup(() => disconnectObserver());
344
424
  const _origReload = instance.reload;
345
- instance.remove = () => {
346
- disconnectObserver();
347
- return _origRemove();
348
- };
349
425
  instance.reload = async () => {
350
426
  disconnectObserver();
351
- const result = await _origReload();
352
- disconnectObserver = observeNetworkRequests(payload, domains, syncScripts, input.src);
353
- return result;
427
+ try {
428
+ return await _origReload();
429
+ } finally {
430
+ if (!cleaned)
431
+ disconnectObserver = observeNetworkRequests(payload, domains, syncScripts, input.src);
432
+ }
354
433
  };
355
434
  syncScripts();
356
435
  }
357
436
  }
358
- markRaw(instance);
359
- instance.toJSON = () => ({ id: instance.id, status: instance.status.value });
360
437
  return instance;
361
438
  }
@@ -11,30 +11,46 @@ export function useScriptEventPage(onChange) {
11
11
  if (import.meta.server)
12
12
  return payload;
13
13
  let lastPayload = { path: "", title: "" };
14
- let stopDomWatcher = () => {
15
- };
14
+ let disposed = false;
15
+ const pendingCleanups = /* @__PURE__ */ new Set();
16
16
  const stopPageFinishHook = nuxt.hooks.hook("page:finish", () => {
17
- Promise.race([
18
- // possibly no head update is needed
19
- new Promise((resolve) => setTimeout(resolve, 100)),
20
- new Promise((resolve) => {
21
- stopDomWatcher = head.hooks.hook("dom:rendered", () => resolve());
22
- })
23
- ]).finally(stopDomWatcher).then(() => {
24
- payload.value = {
25
- path: route.fullPath,
26
- title: document.title
27
- };
28
- if (lastPayload.path !== payload.value.path || lastPayload.title !== payload.value.title) {
29
- if (onChange) {
30
- onChange(payload.value);
17
+ let settled = false;
18
+ let stopDomWatcher = () => {
19
+ };
20
+ const timer = setTimeout(finish, 100);
21
+ function cleanup() {
22
+ clearTimeout(timer);
23
+ stopDomWatcher();
24
+ pendingCleanups.delete(cleanup);
25
+ }
26
+ function finish() {
27
+ if (settled)
28
+ return;
29
+ settled = true;
30
+ cleanup();
31
+ queueMicrotask(() => {
32
+ if (disposed)
33
+ return;
34
+ payload.value = {
35
+ path: route.fullPath,
36
+ title: document.title
37
+ };
38
+ if (lastPayload.path !== payload.value.path || lastPayload.title !== payload.value.title) {
39
+ if (onChange) {
40
+ onChange(payload.value);
41
+ }
42
+ lastPayload = payload.value;
31
43
  }
32
- lastPayload = payload.value;
33
- }
34
- });
44
+ });
45
+ }
46
+ pendingCleanups.add(cleanup);
47
+ stopDomWatcher = head.hooks.hook("dom:rendered", finish);
35
48
  });
36
49
  onScopeDispose(() => {
37
- stopDomWatcher();
50
+ disposed = true;
51
+ for (const cleanup of pendingCleanups)
52
+ cleanup();
53
+ pendingCleanups.clear();
38
54
  stopPageFinishHook();
39
55
  });
40
56
  return payload;
@@ -8,15 +8,29 @@ export function useScriptTriggerIdleTimeout(options) {
8
8
  }
9
9
  const { timeout } = options;
10
10
  return new Promise((resolve) => {
11
+ let disposed = false;
12
+ let settled = false;
13
+ let stopTimer = () => {
14
+ };
15
+ const settle = (value) => {
16
+ if (settled)
17
+ return;
18
+ settled = true;
19
+ stopTimer();
20
+ resolve(value);
21
+ };
22
+ tryOnScopeDispose(() => {
23
+ disposed = true;
24
+ settle(false);
25
+ });
11
26
  onNuxtReady(() => {
27
+ if (disposed)
28
+ return;
12
29
  const { start, stop } = useTimeoutFn(() => {
13
- resolve(true);
30
+ settle(true);
14
31
  }, timeout, { immediate: false });
32
+ stopTimer = stop;
15
33
  start();
16
- tryOnScopeDispose(() => {
17
- stop();
18
- resolve(false);
19
- });
20
34
  });
21
35
  });
22
36
  }
@@ -8,27 +8,40 @@ export function useScriptTriggerInteraction(options) {
8
8
  }
9
9
  const { events, target = document.documentElement } = options;
10
10
  return new Promise((resolve) => {
11
+ let disposed = false;
12
+ let settled = false;
13
+ const cleanupFns = [];
14
+ const cleanup = () => {
15
+ cleanupFns.splice(0).forEach((fn) => fn());
16
+ };
17
+ const settle = (value) => {
18
+ if (settled)
19
+ return;
20
+ settled = true;
21
+ cleanup();
22
+ resolve(value);
23
+ };
24
+ tryOnScopeDispose(() => {
25
+ disposed = true;
26
+ settle(false);
27
+ });
11
28
  onNuxtReady(() => {
29
+ if (disposed)
30
+ return;
12
31
  if (!target) {
13
- resolve(false);
32
+ settle(false);
14
33
  return;
15
34
  }
16
- const cleanupFns = [];
17
35
  events.forEach((event) => {
18
- const cleanup = useEventListener(
36
+ const cleanup2 = useEventListener(
19
37
  target,
20
38
  event,
21
39
  () => {
22
- cleanupFns.forEach((fn) => fn());
23
- resolve(true);
40
+ settle(true);
24
41
  },
25
42
  { once: true, passive: true }
26
43
  );
27
- cleanupFns.push(cleanup);
28
- });
29
- tryOnScopeDispose(() => {
30
- cleanupFns.forEach((fn) => fn());
31
- resolve(false);
44
+ cleanupFns.push(cleanup2);
32
45
  });
33
46
  });
34
47
  });
@@ -9,31 +9,34 @@ export function useScriptTriggerServiceWorker(options) {
9
9
  resolve(true);
10
10
  return;
11
11
  }
12
- let resolved = false;
13
- const done = () => {
14
- if (resolved)
12
+ let settled = false;
13
+ let timer;
14
+ const cleanup = () => {
15
+ navigator.serviceWorker.removeEventListener("controllerchange", onControllerChange);
16
+ if (timer !== void 0) {
17
+ clearTimeout(timer);
18
+ timer = void 0;
19
+ }
20
+ };
21
+ const done = (value) => {
22
+ if (settled)
15
23
  return;
16
- resolved = true;
17
- resolve(true);
24
+ settled = true;
25
+ cleanup();
26
+ resolve(value);
18
27
  };
28
+ function onControllerChange() {
29
+ done(true);
30
+ }
31
+ tryOnScopeDispose(() => done(false));
19
32
  if (navigator.serviceWorker.controller) {
20
- done();
33
+ done(true);
21
34
  return;
22
35
  }
23
- const onControllerChange = () => {
24
- navigator.serviceWorker.removeEventListener("controllerchange", onControllerChange);
25
- done();
26
- };
27
36
  navigator.serviceWorker.addEventListener("controllerchange", onControllerChange);
28
- const timer = setTimeout(() => {
29
- navigator.serviceWorker.removeEventListener("controllerchange", onControllerChange);
37
+ timer = setTimeout(() => {
30
38
  console.warn("[nuxt-scripts] Service worker not controlling after timeout, loading scripts anyway");
31
- done();
39
+ done(true);
32
40
  }, timeout);
33
- tryOnScopeDispose(() => {
34
- navigator.serviceWorker.removeEventListener("controllerchange", onControllerChange);
35
- clearTimeout(timer);
36
- resolve(false);
37
- });
38
41
  });
39
42
  }
@@ -5,15 +5,23 @@ export default defineNuxtPlugin(() => {
5
5
  const version = config.public["nuxt-scripts"]?.version;
6
6
  const firstPartyData = config.public["nuxt-scripts-devtools"] || null;
7
7
  let lastScripts = {};
8
+ let disposed = false;
9
+ let requestController;
8
10
  function pushState(scripts) {
11
+ if (disposed)
12
+ return;
9
13
  if (scripts)
10
14
  lastScripts = scripts;
11
15
  const route = nuxtApp.$router?.currentRoute?.value;
12
16
  if (route?.path?.startsWith("/__nuxt-scripts"))
13
17
  return;
18
+ requestController?.abort();
19
+ const controller = new AbortController();
20
+ requestController = controller;
14
21
  fetch("/__nuxt-scripts-api/state", {
15
22
  method: "POST",
16
23
  headers: { "Content-Type": "application/json" },
24
+ signal: controller.signal,
17
25
  body: JSON.stringify({
18
26
  scripts: serializeScripts(lastScripts),
19
27
  version,
@@ -21,16 +29,33 @@ export default defineNuxtPlugin(() => {
21
29
  route: route ? { path: route.path, fullPath: route.fullPath, query: route.query } : null
22
30
  })
23
31
  }).catch((error) => {
24
- if (import.meta.dev)
32
+ if (import.meta.dev && error?.name !== "AbortError")
25
33
  console.warn("[nuxt-scripts] Failed to sync standalone devtools state:", error);
34
+ }).finally(() => {
35
+ if (requestController === controller)
36
+ requestController = void 0;
26
37
  });
27
38
  }
28
- nuxtApp.hooks.hook("scripts:updated", (ctx) => {
39
+ const stopScriptsHook = nuxtApp.hooks.hook("scripts:updated", (ctx) => {
29
40
  pushState(ctx.scripts);
30
41
  });
31
- nuxtApp.$router?.afterEach(() => {
42
+ const stopRouterHook = nuxtApp.$router?.afterEach(() => {
32
43
  pushState();
33
44
  });
45
+ let stopUnmountHook = () => {
46
+ };
47
+ const cleanup = () => {
48
+ if (disposed)
49
+ return;
50
+ disposed = true;
51
+ requestController?.abort();
52
+ requestController = void 0;
53
+ lastScripts = {};
54
+ stopScriptsHook();
55
+ stopRouterHook?.();
56
+ stopUnmountHook();
57
+ };
58
+ stopUnmountHook = nuxtApp.hooks.hook("app:unmount", cleanup);
34
59
  pushState(nuxtApp._scripts || {});
35
60
  });
36
61
  function serializeScripts(scripts) {
@@ -0,0 +1,7 @@
1
+ type ScriptApi = Record<string | symbol, any>;
2
+ /**
3
+ * Keeps Unhead's recording proxy while an SDK loads, then forwards calls to
4
+ * the resolved SDK without discarding their return values.
5
+ */
6
+ export declare function createNpmScriptProxy<T extends ScriptApi>(fallback: T, getApi: () => T | undefined): T;
7
+ export {};
@@ -0,0 +1,46 @@
1
+ function walk(root, path) {
2
+ let owner;
3
+ let value = root;
4
+ for (const key of path) {
5
+ if (value == null)
6
+ return { owner: void 0, value: void 0 };
7
+ owner = value;
8
+ value = Reflect.get(value, key, value);
9
+ }
10
+ return { owner, value };
11
+ }
12
+ export function createNpmScriptProxy(fallback, getApi) {
13
+ function node(path, fallbackValue) {
14
+ const children = /* @__PURE__ */ new Map();
15
+ return new Proxy(() => {
16
+ }, {
17
+ get(_, property) {
18
+ const api = getApi();
19
+ let value;
20
+ if (api) {
21
+ const current = walk(api, path).value;
22
+ value = current == null ? void 0 : Reflect.get(current, property, current);
23
+ } else {
24
+ value = Reflect.get(fallbackValue, property, fallbackValue);
25
+ }
26
+ if (typeof value !== "function")
27
+ return value;
28
+ let child = children.get(property);
29
+ if (!child) {
30
+ child = node([...path, property], value);
31
+ children.set(property, child);
32
+ }
33
+ return child;
34
+ },
35
+ apply(_, thisArg, args) {
36
+ const api = getApi();
37
+ if (api) {
38
+ const { owner, value } = walk(api, path);
39
+ return Reflect.apply(value, owner, args);
40
+ }
41
+ return Reflect.apply(fallbackValue, thisArg, args);
42
+ }
43
+ });
44
+ }
45
+ return node([], fallback);
46
+ }
@@ -3,7 +3,6 @@ import { logger } from "./logger.js";
3
3
  export function createNpmScriptStub(options) {
4
4
  const status = ref("awaitingLoad");
5
5
  const loadedCallbacks = [];
6
- let initPromise = null;
7
6
  let hasInitialized = false;
8
7
  const proxy = options.use?.() || {};
9
8
  const stub = {
@@ -17,12 +16,11 @@ export function createNpmScriptStub(options) {
17
16
  try {
18
17
  if (options.clientInit) {
19
18
  console.log(`[NpmScriptStub] Initializing ${options.key}...`);
20
- initPromise = Promise.resolve(options.clientInit());
21
- await initPromise;
19
+ await options.clientInit();
22
20
  console.log(`[NpmScriptStub] ${options.key} initialized successfully`);
23
21
  }
24
22
  status.value = "loaded";
25
- loadedCallbacks.forEach((cb) => {
23
+ loadedCallbacks.splice(0).forEach((cb) => {
26
24
  try {
27
25
  cb(proxy);
28
26
  } catch (error) {
@@ -30,6 +28,7 @@ export function createNpmScriptStub(options) {
30
28
  }
31
29
  });
32
30
  } catch (error) {
31
+ loadedCallbacks.splice(0);
33
32
  logger.error(`[NpmScriptStub] Failed to initialize ${options.key}:`, error);
34
33
  status.value = "error";
35
34
  }
@@ -37,7 +36,7 @@ export function createNpmScriptStub(options) {
37
36
  onLoaded(callback) {
38
37
  if (status.value === "loaded") {
39
38
  callback(proxy);
40
- } else {
39
+ } else if (status.value !== "error") {
41
40
  loadedCallbacks.push(callback);
42
41
  }
43
42
  },