Compare commits

...

8 commits
1.2.1 ... main

Author SHA1 Message Date
8615595ad6 Update modules 2025-01-29 22:42:22 +01:00
f621c33de9 Consent API (#1)
# Changelog
- fix typo in supressDnt to suppressDnt setting,
- expose recordConsent and revokeConsent API calls,
- expose Wide Angle API as `useWideAngle()` and remove obsolete `useWaaEvent`

Reviewed-on: https://cloud.inputobjects.eu/forge/forge/wideangle/wideangle-nuxt/pulls/1
Co-authored-by: Jarek Rozanski <jrozanski@inputobjects.eu>
Co-committed-by: Jarek Rozanski <jrozanski@inputobjects.eu>
2025-01-29 20:28:22 +00:00
787a1fd57f Update module tooling 2025-01-02 22:46:27 +01:00
f52bec681c Update dependencies 2025-01-02 22:14:31 +01:00
1f21177997 Reference latest Wide Angle Analytics Vue loader 2025-01-02 22:07:56 +01:00
f8fd8d9567 Packages movie to new repository 2025-01-02 21:57:19 +01:00
Daniel Roe
b51f1f256a
chore: indicate compatibility with new v4 major (#5) 2025-01-02 09:54:36 +01:00
Daniel Roe
967997b2e0
docs: use new nuxi module add command in installation (#4)
* docs: use new `nuxi module add` command in installation

* chore: update block
2025-01-02 09:53:51 +01:00
16 changed files with 4581 additions and 2844 deletions

View file

@ -6,7 +6,7 @@
[![Nuxt][nuxt-src]][nuxt-href]
[![Wide Angle][wideangle-src]][wideangle-href]
![Wide Angle Analytics Large Logo](https://github.com/inputobjects/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
@ -22,7 +22,9 @@ You can enable Wide Angle Analytics in your Nuxt projects in just a few steps. N
2. [Create a new site](https://wideangle.co/documentation/create-and-configure-site) and activate it.
3. Install the `wideangle-vuejs` plugin in your Vue application.
```npm install wideangle-nuxt```
```bash
npx nuxi@latest module add wideangle
```
4. Enable and configure the module.
@ -50,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).
@ -70,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`
}
}
}
@ -83,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});
<template>
<button @click="sendEvent">Send Event</button>
</template>
<script setup>
import { useWideAngle } from '#imports';
const sendEvent = () => {
useWideAngle().dispatchEvent('basket-open', {'page': 'catalogue'}, {'item-price': 599.00, 'basket-total': 1299.00});
}
</script>
```
You will find a fully functional example in this [repository](playground/app.vue).
@ -127,13 +142,14 @@ Example:
</template>
<script setup>
import { useWaaEvent } from '#imports'
import { useWideAngle } from '#imports'
const sendEvent = async () => {
const params = {
session: 'cjhw92nf9aq',
cohort: 'c1233'
}
useWaaEvent('interest', params);
}
useWideAngle().dispatchEvent('interest', params);
}
</script>
```
@ -141,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
<script setup>
import { useWideAngle } from '#imports'
useWideAngle().recordConsent('FHJ44111');
</script>
```
<!-- Badges -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

20
eslint.config.mjs Normal file
View file

@ -0,0 +1,20 @@
// @ts-check
import { createConfigForNuxt } from '@nuxt/eslint-config/flat'
// Run `npx @eslint/config-inspector` to inspect the resolved config interactively
export default createConfigForNuxt({
features: {
// Rules for module authors
tooling: true,
// Rules for formatting
stylistic: true,
},
dirs: {
src: [
'./playground',
],
},
})
.append(
// your custom flat config here...
)

7061
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,12 @@
{
"name": "wideangle-nuxt",
"version": "1.2.1",
"version": "2.0.0",
"description": "Wide Angle Analytics module for Nuxt",
"repository": "inputobjects/wideangle-nuxt",
"homepage": "https://wideangle.co",
"repository": {
"type": "git",
"url": "https://cloud.inputobjects.eu/forge/wideangle/wideangle-nuxt.git"
},
"author": "Wide Angle Analytics <developers@wideangle.co>",
"license": "Apache-2.0",
"type": "module",
@ -13,7 +17,9 @@
"vuejs",
"plugin",
"vuejs plugin",
"nuxt module"
"nuxt module",
"privacy",
"privacy web analytics"
],
"exports": {
".": {
@ -28,29 +34,29 @@
"dist"
],
"scripts": {
"prepack": "nuxt-module-build",
"prepack": "nuxt-module-build build",
"dev": "nuxi dev playground",
"dev:build": "nuxi build playground",
"dev:prepare": "nuxt-module-build --stub && nuxi prepare playground",
"release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
"release": "npm run prepack && changelogen --release && npm publish",
"lint": "eslint .",
"test": "vitest run",
"test:watch": "vitest watch"
"lint:fix": "eslint . --fix"
},
"dependencies": {
"@nuxt/kit": "^3.13.2",
"wideangle-vuejs": "1.0.0",
"defu": "^6.1.2"
"@nuxt/kit": "^3.15.3",
"wideangle-vuejs": "2.0.0",
"defu": "^6.1.4"
},
"devDependencies": {
"@nuxt/eslint-config": "^0.2.0",
"@nuxt/devtools": "latest",
"@nuxt/eslint-config": "^0.7.5",
"@nuxt/module-builder": "^0.8.4",
"@nuxt/schema": "^3.13.2",
"@nuxt/test-utils": "^3.12.0",
"@types/node": "^18",
"changelogen": "^0.5.3",
"eslint": "^8.39.0",
"@nuxt/schema": "^3.15.3",
"@types/node": "latest",
"changelogen": "^0.5.7",
"eslint": "^9.19.0",
"nuxt": "^3.13.2",
"vitest": "^1.4.0"
"typescript": "~5.7.2",
"vue-tsc": "^2.2.0"
}
}

View file

@ -1,36 +0,0 @@
<template>
<main>
<h1>Nuxt Application</h1>
<button @click="trackClick">
Track Click
</button>
</main>
</template>
<script setup lang="ts">
import { useWaaEvent } from "#imports";
function trackClick() {
useWaaEvent("foo", {"name": "bar"}, {"count" : 2});
}
</script>
<style>
main {
font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
font-size: 2rem;
display: flex;
flex-direction: column;
max-width: 300px;
gap: 2rem;
align-items: center;
margin: 0 auto;
text-align: center;
}
button {
padding: 1rem;
}
</style>

View file

@ -0,0 +1,11 @@
<template>
<button @click="sendEvent">Send Event</button>
</template>
<script setup>
import { useWideAngle } from '#imports';
const sendEvent = () => {
useWideAngle().dispatchEvent('foo', {'param1': 'bar'}, {'value1': 123});
}
</script>

View file

@ -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
}
}
}
});
})

View file

@ -1,4 +0,0 @@
{
"private": true,
"name": "wideangle-playground"
}

View file

@ -0,0 +1,7 @@
<template>
<div>
<h1>Wide Angle Analytics Playground</h1>
<sample-tracker/>
</div>
</template>

View file

@ -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<ModuleOptions>({
@ -19,39 +19,29 @@ export default defineNuxtModule<ModuleOptions>({
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.build.transpile.push(resolver.resolve('./runtime'))
nuxt.options.runtimeConfig.public.wideangle = defu(
nuxt.options.runtimeConfig.public.wideangle,
options,
)
nuxt.options.runtimeConfig.public.wideangle ||= {},
{ ...options })
logger.info('Adding Wide Angle Analytics (useWideAngle) import')
addImportsDir(resolver.resolve('./runtime/composables'))
nuxt.options.build.transpile.push(runtimeDir);
const resolver = createResolver(import.meta.url);
logger.info('Adding Wide Angle Analytics runtime plugin');
addImports({
name: "useWaaEvent",
as: "useWaaEvent",
from: resolver.resolve('./runtime/composables/useWaaEvent')
});
addPlugin({
src: resolver.resolve('./runtime/plugin.client')
});
}
logger.info('Adding Wide Angle Analytics runtime plugin')
addPlugin(resolver.resolve('./runtime/plugin.client'))
},
})

View file

@ -1,11 +0,0 @@
import { useNuxtApp } from '#imports';
export function useWaaEvent (name: string, params?: Record<string, any>, values?: Record<string, number>) {
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");
}
}

View file

@ -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;
}
}

View file

@ -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<WideAngleApi>,
},
}
})

5
src/types.ts Normal file
View file

@ -0,0 +1,5 @@
export interface WideAngleApi {
dispatchEvent(name: string, params: any, values: any) : void;
recordConsent(subjectsId: string) : void;
revokeConsent() : void;
}

View file

@ -1,3 +1,8 @@
{
"extends": "./playground/.nuxt/tsconfig.json"
"extends": "./.nuxt/tsconfig.json",
"exclude": [
"dist",
"node_modules",
"playground",
]
}