Revert "chore: remplace SmartTags PA par Piano Analytics"

This reverts commit b9ba32f4d6.
pull/3242/merge
liliced 2024-12-10 14:33:15 +01:00
parent 092c4984d8
commit 1541450983
15 changed files with 147 additions and 178 deletions

View File

@ -3,6 +3,5 @@ dist
storybook-static
site/source/locales/*.yaml
site/source/components/ATInternetTracking/smarttag.js
site/source/components/ATInternetTracking/piano-analytics.js
*.yaml.d.ts
.yarnrc.yml

View File

@ -16,7 +16,6 @@ VITE_ALGOLIA_APP_ID=
VITE_ALGOLIA_SEARCH_KEY=
VITE_ALGOLIA_INDEX_PREFIX=monentreprise-master-
VITE_AT_INTERNET_SITE_ID=
VITE_AT_INTERNET_DEV_SITE_ID=
VITE_FR_BASE_URL="http://localhost:3000/mon-entreprise"
VITE_EN_BASE_URL="http://localhost:3000/infrance"

View File

@ -6,7 +6,7 @@ Content-Security-Policy = """\
style-src 'self' 'unsafe-inline'; \
connect-src 'self' *.incubateur.net raw.githubusercontent.com github.com tm.urssaf.fr recherche-entreprises.api.gouv.fr api.recherche-entreprises.fabrique.social.gouv.fr geo.api.gouv.fr *.algolia.net *.algolianet.com polyfill.io jedonnemonavis.numerique.gouv.fr user-images.githubusercontent.com; \
form-action 'self' *.sibforms.com *.incubateur.net; \
script-src 'self' 'unsafe-inline' 'unsafe-eval' tm.urssaf.fr tag.aticdn.net *.incubateur.net stonly.com code.jquery.com polyfill.io; \
script-src 'self' 'unsafe-inline' 'unsafe-eval' tm.urssaf.fr *.incubateur.net stonly.com code.jquery.com polyfill.io; \
img-src 'self' data: mon-entreprise.urssaf.fr tm.urssaf.fr user-images.githubusercontent.com github.com *.s3.amazonaws.com jedonnemonavis.numerique.gouv.fr; \
frame-src 'self' https://www.youtube-nocookie.com https://codesandbox.io https://place-des-entreprises.beta.gouv.fr https://reso-staging.osc-fr1.scalingo.io https://stackblitz.com https://conseillers-entreprises.service-public.fr \
"""

View File

@ -79,7 +79,6 @@ export default {
input: [
'../../source/**/*.{jsx,tsx,js,ts}',
'!../../source/components/ATInternetTracking/smarttag.js',
'!../../source/components/ATInternetTracking/piano-analytics.js',
],
// An array of globs that describe where to look for source files
// relative to the location of the configuration file

View File

@ -1,3 +1,2 @@
ignorePatterns:
- smarttag.js
- piano-analytics.js

View File

@ -1,9 +1,15 @@
// Ajoute la propriété 'pa' à Window pour que Typescript accepte window.pa
declare global {
interface Window {
pa: ATTracker
}
}
/* eslint-disable no-console */
import './smarttag.js'
// Ci-dessous les indicateurs personnalisés de site et de page
// https://developers.atinternet-solutions.com/javascript-fr/contenus-javascript-fr/indicateurs-de-site-et-de-page-javascript-fr/
export const INDICATOR = {
SITE: {
LANGAGE: 1,
EMBARQUÉ: 2,
},
PAGE: {},
} as const
type PageHit = {
page?: string
@ -11,123 +17,128 @@ type PageHit = {
page_chapter2?: string
page_chapter3?: string
}
type ClickHit = {
click?: string
click_chapter1?: string
click_chapter2?: string
click_chapter3?: string
evenement_type?: 'telechargement'
}
export interface ATTracker {
setConfigurations(options: {
site: number
collectDomain: string
privacyDefaultMode: 'optout' | 'exempt'
}): void
setProperties(
propertiesObject: {
env_language: 'fr' | 'en'
'n:simulateur_embarque': 1 | 0
},
options: {
persistent: true
}
setProp(prop: 'env_language', value: 'fr' | 'en', persistant: true): void
setProp(prop: 'n:simulateur_embarque', value: 1 | 0, persistant: true): void
setProp(
prop: 'evenement_type',
value: 'telechargement',
persistant: false
): void
sendEvent(type: 'page.display', data: PageHit): void
sendEvent(
type:
| 'demarche.document'
| 'click.action'
| 'click.navigation'
| 'click.download'
| 'click.exit',
data: ClickHit & PageHit
): void
events: {
send(type: 'page.display', data: PageHit): void
send(
type:
| 'demarche.document'
| 'click.action'
| 'click.navigation'
| 'click.download'
| 'click.exit',
data: ClickHit & PageHit
): void
}
consent: {
setMode(type: 'exempt' | 'optout'): void
getMode(): { name: 'exempt' | 'optout' }
privacy: {
setVisitorMode(authority: 'cnil', type: 'exempt'): void
setVisitorOptout(): void
getVisitorMode(): { name: 'exempt' | 'optout' }
}
}
type ATTrackerClass = { new (options: { site: number }): ATTracker }
declare global {
const ATInternet: {
Tracker: { Tag: ATTrackerClass }
}
}
export function createTracker(siteId?: string, doNotTrack = false) {
const site = siteId ? +siteId : 0
if (Number.isNaN(site)) {
throw new Error('Expect string siteId to be of number form')
throw new Error('expect string siteId to be of number form')
}
const BaseTracker: ATTrackerClass =
siteId && !import.meta.env.SSR ? ATInternet?.Tracker.Tag : Log
if (typeof window === 'undefined' || !window.pa) {
throw new Error('Piano Analytics script not loaded')
}
class Tag extends BaseTracker implements ATTracker {
#send: ATTracker['events']['send']
class PianoTracker implements ATTracker {
constructor(options: { language: 'fr' | 'en' }) {
window.pa.setConfigurations({
site,
collectDomain: 'https://tm.urssaf.fr',
privacyDefaultMode: doNotTrack ? 'optout' : 'exempt',
})
super({ site })
this.#send = this.events.send.bind(this)
this.events.send = (type, data) => {
if (type === 'page.display') {
this.#currentPageInfo = data
this.#send(type, data)
window.pa.setProperties(
{
env_language: options.language,
'n:simulateur_embarque': document.location.pathname.includes(
'/iframes/'
)
? 1
: 0,
},
{ persistent: true }
return
}
if (!('click' in data)) {
throw new Error('invalid argument error')
}
this.#send(type, { ...this.#currentPageInfo, ...data })
}
this.setProp('env_language', options.language, true)
this.setProp(
'n:simulateur_embarque',
document.location.pathname.includes('/iframes/') ? 1 : 0,
true
)
}
setConfigurations(options: {
site: number
collectDomain: string
privacyDefaultMode: 'exempt'
}): void {
window.pa.setConfigurations(options)
}
setProperties(
propertiesObject: {
env_language: 'fr' | 'en'
'n:simulateur_embarque': 1 | 0
},
options: { persistent: true }
): void {
window.pa.setProperties(propertiesObject, options)
}
sendEvent(
type:
| 'page.display'
| 'demarche.document'
| 'click.action'
| 'click.navigation'
| 'click.download'
| 'click.exit',
data: PageHit | (ClickHit & PageHit)
): void {
if (type === 'page.display') {
window.pa.sendEvent(type, data as PageHit)
if (import.meta.env.MODE === 'production' && doNotTrack) {
this.privacy.setVisitorOptout()
} else {
window.pa.sendEvent(type, data as ClickHit & PageHit)
this.privacy.setVisitorMode('cnil', 'exempt')
}
}
consent = {
setMode(type: 'exempt' | 'optout'): void {
window.pa.consent.setMode(type)
},
getMode(): { name: 'exempt' | 'optout' } {
return window.pa.consent.getMode()
},
}
#currentPageInfo: PageHit = {}
}
return PianoTracker
return Tag
}
export class Log implements ATTracker {
constructor(options?: Record<string, string | number>) {
console.debug('ATTracker::new', options)
}
setProp(name: string, value: unknown, persistent: boolean): void {
console.debug('ATTracker::setProp', { name, value, persistent })
}
events = {
send(name: string, data: Record<string, unknown>): void {
console.debug('ATTracker::events.send', name, data)
},
}
privacy: ATTracker['privacy'] = {
setVisitorMode(...args) {
console.debug('ATTracker::privacy.setVisitorMode', ...args)
},
setVisitorOptout() {
console.debug('ATTracker::setVisitorOptout')
},
getVisitorMode() {
console.debug('ATTracker::privacy.getVisitorMode')
return { name: 'exempt' }
},
}
dispatch(): void {
console.debug('ATTracker::dispatch')
}
}

View File

@ -1,8 +1,8 @@
import React, { createContext, useContext, useEffect } from 'react'
import { ATTracker } from './Tracker'
import { ATTracker, Log } from './Tracker'
export const TrackingContext = createContext<ATTracker | null>(null)
export const TrackingContext = createContext<ATTracker>(new Log())
// From https://github.com/nclsmitchell/at-internet
export function toAtString(string: string): string {
@ -80,7 +80,7 @@ export function TrackPage({
const { chapter1, chapter2, chapter3 } = useChapters(chapters)
const tag = useContext(TrackingContext)
useEffect(() => {
tag?.sendEvent(
tag.events.send(
'page.display',
Object.fromEntries(
Object.entries({

View File

@ -66,7 +66,7 @@ export function Feedback({
const submitFeedback = useCallback(
(rating: FeedbackT) => {
setFeedbackGivenForUrl(url)
tag?.sendEvent('click.action', {
tag.events.send('click.action', {
click_chapter1: 'satisfaction',
click: rating,
})

View File

@ -2,7 +2,7 @@ import { OverlayProvider } from '@react-aria/overlays'
import { ErrorBoundary } from '@sentry/react'
import i18next from 'i18next'
import Engine from 'publicodes'
import { createContext, ReactNode, useEffect, useState } from 'react'
import { createContext, ReactNode } from 'react'
import { HelmetProvider } from 'react-helmet-async'
import { I18nextProvider } from 'react-i18next'
import { Provider as ReduxProvider } from 'react-redux'
@ -16,7 +16,7 @@ import { EmbededContextProvider } from '@/hooks/useIsEmbedded'
import * as safeLocalStorage from '../storage/safeLocalStorage'
import { makeStore } from '../store/store'
import { TrackingContext } from './ATInternetTracking'
import { ATTracker, createTracker } from './ATInternetTracking/Tracker'
import { createTracker } from './ATInternetTracking/Tracker'
import { ErrorFallback } from './ErrorPage'
import { IframeResizer } from './IframeResizer'
import { ServiceWorker } from './ServiceWorker'
@ -92,69 +92,28 @@ function BrowserRouterProvider({
return <>{children}</>
}
const ATTracker = createTracker(
import.meta.env.VITE_AT_INTERNET_SITE_ID,
safeLocalStorage.getItem('tracking:do_not_track') === '1' ||
navigator.doNotTrack === '1'
)
return (
<HelmetProvider>
<TrackingProvider>
<TrackingContext.Provider
value={
new ATTracker({
language: i18next.language as 'fr' | 'en',
})
}
>
<BrowserRouter
basename={import.meta.env.MODE === 'production' ? '' : basename}
future={{ v7_startTransition: true }}
>
{children}
</BrowserRouter>
</TrackingProvider>
</TrackingContext.Provider>
</HelmetProvider>
)
}
function TrackingProvider({ children }: { children: React.ReactNode }) {
const [tracker, setTracker] = useState<ATTracker | null>(null)
useEffect(() => {
const script = document.createElement('script')
script.src = 'https://tag.aticdn.net/piano-analytics.js'
script.type = 'text/javascript'
script.crossOrigin = 'anonymous'
script.async = true
script.onload = () => {
const siteId = (
!import.meta.env.SSR
? import.meta.env.VITE_AT_INTERNET_SITE_ID
: import.meta.env.VITE_AT_INTERNET_DEV_SITE_ID
) as string
const ATTrackerClass = createTracker(
siteId,
safeLocalStorage.getItem('tracking:do_not_track') === '1' ||
navigator.doNotTrack === '1'
)
const instance = new ATTrackerClass({
language: i18next.language as 'fr' | 'en',
})
setTracker(instance)
}
script.onerror = () => {
// eslint-disable-next-line no-console
console.error('Failed to load Piano Analytics script')
}
document.body.appendChild(script)
return () => {
document.body.removeChild(script)
}
}, [])
if (!tracker) {
return <>{children}</>
}
return (
<TrackingContext.Provider value={tracker}>
{children}
</TrackingContext.Provider>
)
}

View File

@ -52,7 +52,7 @@ export function ShareSimulationPopup({ url }: { url: string }) {
<Button
size="XS"
onPress={() => {
tracker?.sendEvent('click.action', {
tracker.events.send('click.action', {
click_chapter1: 'feature:partage',
click: 'lien copié',
})

View File

@ -102,7 +102,7 @@ export default function ShareOrSaveSimulationBanner({
light
size="XS"
onPress={(e) => {
tracker?.sendEvent('click.action', {
tracker.events.send('click.action', {
click_chapter1: 'feature:partage',
click: 'démarré',
})

View File

@ -42,15 +42,15 @@ export default function PrivacyPolicy({
const handleChange = useCallback(
(checked: boolean) => {
if (checked) {
tracker?.consent.setMode('optout')
tracker.privacy.setVisitorOptout()
safeLocalStorage.setItem('tracking:do_not_track', '1')
} else {
tracker?.consent.setMode('exempt')
tracker.privacy.setVisitorMode('cnil', 'exempt')
safeLocalStorage.setItem('tracking:do_not_track', '0')
}
setValueChanged(true)
},
[setValueChanged, tracker?.consent]
[setValueChanged, tracker.privacy]
)
return (
@ -273,10 +273,9 @@ export default function PrivacyPolicy({
<Body>
<Trans i18nKey="privacyPolicy.tracking.content">
mon-entreprise.urssaf.fr ne dépose pas de cookies ou de traceurs.
Cependant, la plateforme utilise Piano Analytics, une solution de
mesure d'audience, configurée en mode « exempté » et ne nécessitant
pas le recueil du consentement des personnes concernées conformément
aux{' '}
Cependant, la plateforme utilise Matomo, une solution de mesure
d'audience, configurée en mode « exempté » et ne nécessitant pas le
recueil du consentement des personnes concernées conformément aux{' '}
<StyledLink
href="https://www.cnil.fr/fr/solutions-pour-les-cookies-de-mesure-daudience"
aria-label={t(
@ -302,7 +301,7 @@ export default function PrivacyPolicy({
<Checkbox
name="opt-out mesure audience"
onChange={handleChange}
defaultSelected={tracker?.consent.getMode().name === 'optout'}
defaultSelected={tracker.privacy.getVisitorMode().name === 'optout'}
>
{t(
'privacyPolicy.tracking.optOut.checkboxLabel',

View File

@ -1686,10 +1686,10 @@ privacyPolicy:
tracking:
ariaLabel: CNIL recommendations, see more information on the CNIL website, new window
content: mon-entreprise.urssaf.fr does not use cookies or other tracking
devices. However, the platform uses Piano Analytics, an audience
measurement solution configured in "exempted" mode, which does not require
the consent of the persons concerned, in accordance with the
<2>recommendations of the CNIL</2>.
devices. However, the platform uses Matomo, an audience measurement
solution, configured in “exempted” mode and not requiring the consent of
the persons concerned in accordance with the <2>recommendations of the
CNIL</2>.
optOut:
checkboxLabel: I do not want to send anonymous data about my use of the platform
for audience measurement purposes.

View File

@ -1794,7 +1794,7 @@ privacyPolicy:
ariaLabel: recommandations de la CNIL, voir plus d'informations à ce sujet sur
le site de la CNIL, nouvelle fenêtre
content: mon-entreprise.urssaf.fr ne dépose pas de cookies ou de traceurs.
Cependant, la plateforme utilise Piano Analytics, une solution de mesure
Cependant, la plateforme utilise Matomo, une solution de mesure
d'audience, configurée en mode « exempté » et ne nécessitant pas le
recueil du consentement des personnes concernées conformément aux
<2>recommandations de la CNIL</2>.

View File

@ -214,9 +214,13 @@ export default function EndBlock({ fields, missingValues }: EndBlockProps) {
href={url}
size="XL"
onPress={() => {
tracker?.sendEvent('demarche.document', {
tracker.setProp(
'evenement_type',
'telechargement',
false
)
tracker.events.send('demarche.document', {
click: 'demande_formulaire_a1',
evenement_type: 'telechargement',
})
}}
download="demande-mobilité-internationale.pdf"