diff --git a/README.md b/README.md index 961b004..d9c564a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Nuxt][nuxt-src]][nuxt-href] [![Wide Angle][wideangle-src]][wideangle-href] -![Wide Angle Analytics Large Logo](https://github.com/wideangleanalytics/wideangle-nuxt/assets/4896588/7efee4f1-d9e1-4b54-a5cd-257d13494f41) +![Wide Angle Analytics Large Logo](assets/full_logo_color_light_transparent.png) # Wide Angle Analytics module for Nuxt @@ -52,10 +52,11 @@ option|description|required|default|example siteId| The Site ID from the Wide Angle Site settings| :white_check_mark: | _none_ | 8D27G3B9ACA01F4241 domain| Domain hosting the script, can be found in Wide Angle Analytics Site settings | :x: | stats.wideangle.co | your.domain.com fingerprint | Should script use browser fingerprinting; this might require collecting consent depending on the applicable laws | :x: | false | true -supressDnt | Should script ingore Do Not Track browser setting. If not enabled, no events will be sent if user's browser has DNT enabled | :x: | false | true +suppressDnt | Should script ingore Do Not Track browser setting. If not enabled, no events will be sent if user's browser has DNT enabled | :x: | false | true includeParams | An array of query parameters that can be passed as part of tracking event. By default only `utm_*` and `ref` parameters are passed in the event | :x: | `[]` | `['sessionId', 'offset']` excludePaths | An array of URL paths that should not trigger default events such as page view, page leave | :x: | `[]` | `['^/wp-admin/.*', ]` ignoreHash | If enabled, a change in the URL fragment will not trigger page view event | :x: | false | true +consentMarker | Name of cookie (with or without) which presence is treated as implied consent; when not defined, consent is not determined by cookie | :x: | n/a | `WAA_CONSENT=true` You can find more details about these settings in the [Wide Angle Analytics documentation](https://wideangle.co/documentation/configure-site). @@ -72,10 +73,11 @@ export default defineNuxtConfig({ siteId: "8D27G3B9ACA01F4241", domain: "your.domain.com", fingerprint: false, - supressDnt: true, + suppressDnt: true, includeParams: ['q', 'customerId'], excludePaths: ['^/admin.*'], - ignoreHash: true + ignoreHash: true, + consentMarker: `WAA_CONSENT=true` } } } @@ -85,10 +87,21 @@ export default defineNuxtConfig({ # Usage -The Wide Angle Analytics provides an instance of `waa` which can be then injected to your component. +The Wide Angle Analytics provides a composable which can be used in your component. ```javascript -useWaaEvent('purchase', {'basket_element': 'dress'}, {'basket_item_price': 123.44}); + + + + ``` You will find a fully functional example in this [repository](playground/app.vue). @@ -129,13 +142,14 @@ Example: ``` @@ -143,6 +157,59 @@ const sendEvent = async () => { ### Module Assets You can find a high-resolution Wide Angle Analytics logo and icon on our [media page](https://wideangle.co/media). +# Recording consent + +The Wide Angle Analytics, thanks to is privacy-first, anonymous approach to web traffic analytics does not requires consent by default. + +However, we do offer multiple tools that support collecting consent should it be required in your use case. + +## Opt-Out by default + +If the visitors browsers has `DoNotTrack` setting enabled in the browser, it will be understood as opt-out and not tracking events will be issued. + +You website can't overwrite this behaviour by specifying `suppressDnt` setting. + +```javascript +wideangle: { + siteId: "8D27G3B9ACA01F4241", + suppressDnt: true +} +``` + +## Opt-In or Opt-Out based on Cookie + +Wide Angle can be configure to handle presence of a cookie, or a cookie with specific value, as an implicit consent. Lack of the cookie will be handled as implicit opt-out. + +Example configuration with cookie marker, expecting cookie name `WAA_CONSENT` with value `true`: + +```javascript +wideangle: { + siteId: "8D27G3B9ACA01F4241", + consentMarker: "WAA_CONSENT=true" +} +``` + +## Programmatic consent + +The Wide Angle serving offers two additional methods, which allow for recording tracking consent: + +- `recordConsent(subjectId: String): void`, and +- `revokeConsent()` + + +Calling above methods on `waa` service will overwrite other consent mechanism (ie. DoNotTrack, and cookie marker). + +Example usage: + +```vue + +``` diff --git a/assets/full_logo_color_light_transparent.png b/assets/full_logo_color_light_transparent.png new file mode 100755 index 0000000..6f98b15 Binary files /dev/null and b/assets/full_logo_color_light_transparent.png differ diff --git a/package.json b/package.json index db560cb..f9eb1fa 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,12 @@ { "name": "wideangle-nuxt", - "version": "1.2.2", + "version": "2.0.0", "description": "Wide Angle Analytics module for Nuxt", - "repository": "wideangleanalytics/wideangle-nuxt", + "homepage": "https://wideangle.co", + "repository": { + "type": "git", + "url": "https://cloud.inputobjects.eu/forge/wideangle/wideangle-nuxt.git" + }, "author": "Wide Angle Analytics ", "license": "Apache-2.0", "type": "module", @@ -34,19 +38,20 @@ "dev": "nuxi dev playground", "dev:build": "nuxi build playground", "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground", - "release": "npm run lint && npm run prepack && changelogen --release && npm publish && git push --follow-tags", - "lint": "eslint ." + "release": "npm lint && npm test && npm prepack && changelogen --release && npm publish && git push --follow-tags", + "lint": "eslint .", + "lint:fix": "eslint . --fix" }, "dependencies": { "@nuxt/kit": "^3.13.2", - "wideangle-vuejs": "1.0.1", + "wideangle-vuejs": "2.0.0", "defu": "^6.1.2" }, "devDependencies": { - "@nuxt/eslint-config": "^0.7.4", - "@nuxt/module-builder": "^0.8.4", - "@nuxt/schema": "^3.13.2", - "@nuxt/test-utils": "^3.12.0", + "@nuxt/devtools": "latest", + "@nuxt/eslint-config": "^0.3.13", + "@nuxt/module-builder": "^0.8.1", + "@nuxt/schema": "^3.12.4", "@types/node": "^18", "changelogen": "^0.5.3", "eslint": "^9.17.0", diff --git a/playground/app.vue b/playground/app.vue deleted file mode 100644 index 0bec44b..0000000 --- a/playground/app.vue +++ /dev/null @@ -1,36 +0,0 @@ - - - - - diff --git a/playground/components/sample-tracker.vue b/playground/components/sample-tracker.vue new file mode 100644 index 0000000..95b3c49 --- /dev/null +++ b/playground/components/sample-tracker.vue @@ -0,0 +1,11 @@ + + + diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index 1c2483a..26526db 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -1,12 +1,17 @@ +// https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ - modules: ['../src/module'], + devtools: { enabled: true }, + compatibilityDate: "2025-01-28", + modules: ['wideangle-nuxt'], + // ssr: false, runtimeConfig: { public: { wideangle: { - siteId: "7982G3B9ACB1BF4380", - fingerprint: true, - supressDnt: true + siteId: "8D27G3B9ACA01F4241", + domain: "events.wideangle.test", + fingerprint: false, + suppressDnt: true } } } -}); +}) diff --git a/playground/package.json b/playground/package.json deleted file mode 100644 index 6a7f1ae..0000000 --- a/playground/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "private": true, - "name": "wideangle-playground" -} diff --git a/playground/pages/index.vue b/playground/pages/index.vue new file mode 100644 index 0000000..69ee7a6 --- /dev/null +++ b/playground/pages/index.vue @@ -0,0 +1,7 @@ + + diff --git a/src/module.ts b/src/module.ts index 2ca3082..5417971 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,6 +1,5 @@ -import { defineNuxtModule, addPlugin, addImports, createResolver, useLogger } from '@nuxt/kit' +import { defineNuxtModule, addPlugin, addImportsDir, createResolver, useLogger } from '@nuxt/kit' import { defu } from 'defu' -import { fileURLToPath } from 'url' const logger = useLogger('nuxt:wideangle') @@ -8,10 +7,11 @@ export interface ModuleOptions { siteId?: string domain: string fingerprint: boolean - supressDnt: boolean + suppressDnt: boolean includeParams: string[] excludePaths: string[] ignoreHash: boolean + consentMarker: string } export default defineNuxtModule({ @@ -19,39 +19,32 @@ export default defineNuxtModule({ name: 'wideangle', configKey: 'wideangle', compatibility: { - nuxt: '>=3' - } + nuxt: '>=3', + }, }, defaults: { - domain: "stats.wideangle.co", + domain: 'stats.wideangle.co', fingerprint: false, - supressDnt: false, + suppressDnt: false, includeParams: [], excludePaths: [], - ignoreHash: false + ignoreHash: false, + consentMarker: undefined, }, - setup (options, nuxt) { - const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url)) - + setup(options, nuxt) { + const resolver = createResolver(import.meta.url) nuxt.options.runtimeConfig.public.wideangle = defu( nuxt.options.runtimeConfig.public.wideangle, options, ) - nuxt.options.build.transpile.push(runtimeDir); - const resolver = createResolver(import.meta.url); + nuxt.options.build.transpile.push(resolver.resolve('./runtime')) - logger.info('Adding Wide Angle Analytics runtime plugin'); + logger.info('Adding Wide Angle Analytics (useWideAngle) import') + addImportsDir(resolver.resolve('./runtime/composables')) - addImports({ - name: "useWaaEvent", - as: "useWaaEvent", - from: resolver.resolve('./runtime/composables/useWaaEvent') - }); + logger.info('Adding Wide Angle Analytics runtime plugin') + addPlugin(resolver.resolve('./runtime/plugin.client')) - addPlugin({ - src: resolver.resolve('./runtime/plugin.client') - }); - - } + }, }) diff --git a/src/runtime/composables/useWaaEvent.ts b/src/runtime/composables/useWaaEvent.ts deleted file mode 100644 index 6deabe0..0000000 --- a/src/runtime/composables/useWaaEvent.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useNuxtApp } from '#imports'; - -export function useWaaEvent (name: string, params?: Record, values?: Record) { - const waa = useNuxtApp().$waa - console.debug(`[WAA] Attempting to send Wide Angle event: ${name}`); - if(waa && waa.value) { - waa.value.dispatchEvent(name, params, values); - } else { - console.debug("[WAA] Wide Angle Analytics is not yest initialized"); - } -} diff --git a/src/runtime/composables/useWideAngle.ts b/src/runtime/composables/useWideAngle.ts new file mode 100644 index 0000000..1c9ab74 --- /dev/null +++ b/src/runtime/composables/useWideAngle.ts @@ -0,0 +1,27 @@ +import {useNuxtApp} from '#imports' +import type {WideAngleApi} from "~/src/types"; + +class NoOpWideAngleAnalyticsApi implements WideAngleApi { + dispatchEvent(name: string, params: any, values: any): void { + console.debug(`[WideAngleApi#dispatchEvent] Defaulting to NoOp Wide Angle call with name "${name}", params: ${JSON.stringify(params)}, values: ${JSON.stringify(params)}`); + } + + recordConsent(subjectsId: string): void { + console.debug(`[WideAngleApi#recordConsent] Defaulting to NoOp Wide Angle call with ${subjectsId}`); + } + + revokeConsent() { + console.debug(`[WideAngleApi#revokeConsent] Defaulting to NoOp Wide Angle call`); + } +} + +const noOpWideAngleApi = new NoOpWideAngleAnalyticsApi(); + +export function useWideAngle() { + const { $waa } = useNuxtApp() + if ($waa) { + return $waa.value; + } else { + return noOpWideAngleApi; + } +} diff --git a/src/runtime/plugin.client.ts b/src/runtime/plugin.client.ts index c44cddc..4ba97cf 100644 --- a/src/runtime/plugin.client.ts +++ b/src/runtime/plugin.client.ts @@ -1,30 +1,36 @@ -import { defineNuxtPlugin, useRuntimeConfig } from '#imports'; -import { ref } from 'vue'; -import { initWideAngle } from 'wideangle-vuejs'; +import { type Ref, ref} from 'vue' +import { initWideAngle } from 'wideangle-vuejs' +import { defineNuxtPlugin, type NuxtApp } from '#app' +import { useRuntimeConfig } from "#imports"; +import type { WideAngleApi } from "~/src/types"; -export default defineNuxtPlugin(() => { - if(import.meta.server) { - console.warn("[WAA] Plugin will not be enabled on server side."); - return; +export default defineNuxtPlugin(async (_nuxtApp) => { + + if (import.meta.server) { + console.warn('[WAA] Plugin will not be enabled on server side.') + return } const { wideangle: options } = useRuntimeConfig().public - console.debug(`[WAA] Initializing Wide Angle Analytics with: ${JSON.stringify(options)}`); - if(options.siteId == null) { - throw new Error("[WAA] Wide Angle Analytics requires the site ID."); + console.debug(`[WAA] Initializing Wide Angle Analytics with: ${JSON.stringify(options)}`) + + if (options.siteId == null) { + throw new Error('[WAA] Wide Angle Analytics requires the site ID.') } + const waa = ref() + initWideAngle(options) - .then(waaInstance => { - waa.value = waaInstance; - console.debug("[WAA] Wide Angle Analytics instance available"); - }).catch(e => { console.error("[WAA] Failed to load Wide Angle Plugin", e)}); + .then((waaInstance) => { + waa.value = waaInstance + console.debug('[WAA] Wide Angle Analytics instance available') + }).catch((e) => { + console.error('[WAA] Failed to load Wide Angle Plugin', e) + }) return { provide: { - waa - } + waa: waa as Ref, + }, } }) - - diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..d326b5a --- /dev/null +++ b/src/types.ts @@ -0,0 +1,5 @@ +export interface WideAngleApi { + dispatchEvent(name: string, params: any, values: any) : void; + recordConsent(subjectsId: string) : void; + revokeConsent() : void; +}