From 4d6b8d6a8c99725073afe738b17e7e7e52e8c626 Mon Sep 17 00:00:00 2001 From: Felix Gabler <27334472+felixgabler@users.noreply.github.com> Date: Fri, 25 Sep 2026 11:49:17 +0000 Subject: [PATCH] fix(meta-pixel): let a pre-load consent call replace the queued default --- .../script/src/runtime/registry/meta-pixel.ts | 10 ++++++ .../nuxt-runtime/consent-default.nuxt.test.ts | 31 +++++++++++++++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/packages/script/src/runtime/registry/meta-pixel.ts b/packages/script/src/runtime/registry/meta-pixel.ts index c6ccdf24b..82f5780f4 100644 --- a/packages/script/src/runtime/registry/meta-pixel.ts +++ b/packages/script/src/runtime/registry/meta-pixel.ts @@ -84,6 +84,16 @@ export function useScriptMetaPixel(_options?: MetaPixelI // @ts-expect-error untyped fbq.callMethod(...params) } + else if (params[0] === 'consent') { + // fbevents replays this queue in order and stops at a revoke, so a grant queued + // behind one never runs. Before it loads, consent replaces the pending state + // ahead of every queued command. + for (let i = fbq.queue.length - 1; i >= 0; i--) { + if (fbq.queue[i][0] === 'consent') + fbq.queue.splice(i, 1) + } + fbq.queue.unshift(params) + } else { fbq.queue.push(params) } diff --git a/test/nuxt-runtime/consent-default.nuxt.test.ts b/test/nuxt-runtime/consent-default.nuxt.test.ts index 2c7e26489..0f02605d0 100644 --- a/test/nuxt-runtime/consent-default.nuxt.test.ts +++ b/test/nuxt-runtime/consent-default.nuxt.test.ts @@ -463,19 +463,44 @@ describe('per-script consent object', () => { expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining('ad_storage')) }) - it('meta: consent.grant()/revoke() queue fbq(\'consent\', ...) calls', async () => { + it('meta: consent.grant()/revoke() before load keep only the latest consent at the head of the queue', async () => { const { useScriptMetaPixel } = await import('../../packages/script/src/runtime/registry/meta-pixel') const result: any = useScriptMetaPixel({ id: '123' }) result._opts.clientInit() - ;(window as any).fbq.queue = [] result.consent.grant() result.consent.revoke() expect((window as any).fbq.queue).toEqual([ - ['consent', 'grant'], ['consent', 'revoke'], + ['init', '123'], + ['track', 'PageView'], + ]) + }) + + it('meta: consent.grant() before load replaces defaultConsent \'denied\' instead of queueing behind it', async () => { + const { useScriptMetaPixel } = await import('../../packages/script/src/runtime/registry/meta-pixel') + const result: any = useScriptMetaPixel({ id: '123', defaultConsent: 'denied' }) + result._opts.clientInit() + ;(window as any).fbq('track', 'Lead') + result.consent.grant() + expect((window as any).fbq.queue).toEqual([ + ['consent', 'grant'], + ['init', '123'], + ['track', 'PageView'], + ['track', 'Lead'], ]) }) + it('meta: consent calls go straight to fbevents once it has loaded', async () => { + const { useScriptMetaPixel } = await import('../../packages/script/src/runtime/registry/meta-pixel') + const result: any = useScriptMetaPixel({ id: '123', defaultConsent: 'denied' }) + result._opts.clientInit() + const callMethod = vi.fn() + ;(window as any).fbq.callMethod = callMethod + result.consent.grant() + expect(callMethod).toHaveBeenCalledWith('consent', 'grant') + expect((window as any).fbq.queue[0]).toEqual(['consent', 'revoke']) + }) + it('tiktok: consent.grant()/revoke()/hold() queue ttq consent actions', async () => { const { useScriptTikTokPixel } = await import('../../packages/script/src/runtime/registry/tiktok-pixel') const result: any = useScriptTikTokPixel({ id: 'CA123' })