Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 25 additions & 8 deletions playground/pages/features/cookie-consent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ function acceptCookies() {
scriptConsent.accept()
showCookieBanner.value = false
}
function revokeCookies() {
scriptConsent.revoke()
showCookieBanner.value = true
}
useScriptGoogleTagManager({
id: 'GTM-MWW974PF',
scriptOptions: {
Expand All @@ -18,16 +22,29 @@ useScriptGoogleTagManager({
</script>

<template>
<div v-if="showCookieBanner" id="cookie-consent" class="p-5 bg-blue-900">
<div class="font-bold mb-2">
Do you accept cookies?
<div>
<div v-if="showCookieBanner" id="cookie-consent" class="p-5 bg-blue-900">
<div class="font-bold mb-2">
Do you accept cookies?
</div>
<div class="flex items-center gap-4">
<UButton @click="acceptCookies">
Yes
</UButton>
<UButton @click="showCookieBanner = false">
No
</UButton>
</div>
</div>
<div class="flex items-center gap-4">
<UButton @click="acceptCookies">
Yes
<div v-else class="p-5 bg-gray-100">
<div class="font-bold mb-2">
Cookie Status: {{ scriptConsent.consented.value ? 'Accepted' : 'Declined' }}
</div>
<UButton v-if="scriptConsent.consented.value" color="red" @click="revokeCookies">
Revoke Consent
</UButton>
<UButton @click="showCookieBanner = false">
No
<UButton v-else color="green" @click="acceptCookies">
Accept Cookies
</UButton>
</div>
</div>
Expand Down
75 changes: 47 additions & 28 deletions src/runtime/composables/useScriptTriggerConsent.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isRef, ref, toValue, watch } from 'vue'
import { isRef, type Ref, ref, toValue, watch } from 'vue'
import { tryUseNuxtApp, onNuxtReady, requestIdleCallback } from 'nuxt/app'
import type { ConsentScriptTriggerOptions } from '../types'

Expand All @@ -7,28 +7,53 @@ interface UseConsentScriptTriggerApi extends Promise<void> {
* A function that can be called to accept the consent and load the script.
*/
accept: () => void
/**
* A function that can be called to revoke the consent and unload the script.
*/
revoke: () => void
/**
* Reactive reference to the consent state
*/
consented: Ref<boolean>
}

/**
* Load a script once consent has been provided either through a resolvable `consent` or calling the `accept` method.
* Supports revoking consent which will unload the script by rejecting the trigger promise.
* @param options
*/
export function useScriptTriggerConsent(options?: ConsentScriptTriggerOptions): UseConsentScriptTriggerApi {
if (import.meta.server)
return new Promise(() => {}) as UseConsentScriptTriggerApi

const consented = ref<boolean>(false)
// user may want ot still load the script on idle
const nuxtApp = tryUseNuxtApp()
const promise = new Promise<void>((resolve) => {
watch(consented, (ready) => {
if (ready) {

// Setup initial consent value
if (options?.consent) {
if (isRef(options?.consent)) {
watch(options.consent, (_val) => {
const val = toValue(_val)
consented.value = Boolean(val)
}, { immediate: true })
}
// check for boolean primitive
else if (typeof options?.consent === 'boolean') {
consented.value = options?.consent
}
// consent is a promise
else if (options?.consent instanceof Promise) {
options?.consent.then((res) => {
consented.value = typeof res === 'boolean' ? res : true
})
}
}

const promise = new Promise<void>((resolve, reject) => {
watch(consented, (newValue, oldValue) => {
if (newValue && !oldValue) {
// Consent granted - load script
const runner = nuxtApp?.runWithContext || ((cb: () => void) => cb())
// TODO drop support in v1
if (options?.postConsentTrigger instanceof Promise) {
options.postConsentTrigger.then(() => runner(resolve))
return
}
if (typeof options?.postConsentTrigger === 'function') {
// check if function has an argument
if (options?.postConsentTrigger.length === 1) {
Expand All @@ -50,29 +75,23 @@ export function useScriptTriggerConsent(options?: ConsentScriptTriggerOptions):
// other trigger not supported
runner(resolve)
}
})
if (options?.consent) {
if (isRef(options?.consent)) {
watch(options.consent, (_val) => {
const val = toValue(_val)
consented.value = Boolean(val)
}, { immediate: true })
}
// check for boolean primitive
else if (typeof options?.consent === 'boolean') {
consented.value = options?.consent
else if (!newValue && oldValue) {
// Consent revoked - trigger rejection to signal script should be unloaded
reject(new Error('Consent revoked'))
}
// consent is a promise
else if (options?.consent instanceof Promise) {
options?.consent.then((res) => {
consented.value = typeof res === 'boolean' ? res : true
})
}
}
})
}) as UseConsentScriptTriggerApi

// we augment the promise with a consent API
promise.accept = () => {
consented.value = true
}

promise.revoke = () => {
consented.value = false
}

promise.consented = consented

return promise as UseConsentScriptTriggerApi
}
Loading