chore: remplace SmartTags PA par Piano Analytics
(cherry picked from commit b9ba32f4d6
)
pull/3254/head
parent
a017419086
commit
c5b59d3074
|
@ -3,5 +3,6 @@ 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
|
|
@ -16,6 +16,7 @@ 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"
|
||||
|
|
|
@ -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 *.incubateur.net stonly.com code.jquery.com polyfill.io; \
|
||||
script-src 'self' 'unsafe-inline' 'unsafe-eval' tm.urssaf.fr tag.aticdn.net *.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 \
|
||||
"""
|
||||
|
|
|
@ -79,6 +79,7 @@ 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
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
ignorePatterns:
|
||||
- smarttag.js
|
||||
- piano-analytics.js
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
/* 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
|
||||
// Ajoute la propriété 'pa' à Window pour que Typescript accepte window.pa
|
||||
declare global {
|
||||
interface Window {
|
||||
pa: ATTracker
|
||||
}
|
||||
}
|
||||
|
||||
type PageHit = {
|
||||
page?: string
|
||||
|
@ -17,128 +11,123 @@ 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 {
|
||||
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
|
||||
setConfigurations(options: {
|
||||
site: number
|
||||
collectDomain: string
|
||||
privacyDefaultMode: 'optout' | 'exempt'
|
||||
}): void
|
||||
|
||||
setProperties(
|
||||
propertiesObject: {
|
||||
env_language: 'fr' | 'en'
|
||||
'n:simulateur_embarque': 1 | 0
|
||||
},
|
||||
options: {
|
||||
persistent: true
|
||||
}
|
||||
): 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
|
||||
}
|
||||
sendEvent(type: 'page.display', data: PageHit): void
|
||||
sendEvent(
|
||||
type:
|
||||
| 'demarche.document'
|
||||
| 'click.action'
|
||||
| 'click.navigation'
|
||||
| 'click.download'
|
||||
| 'click.exit',
|
||||
data: ClickHit & PageHit
|
||||
): void
|
||||
|
||||
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 }
|
||||
consent: {
|
||||
setMode(type: 'exempt' | 'optout'): void
|
||||
getMode(): { name: 'exempt' | 'optout' }
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
class Tag extends BaseTracker implements ATTracker {
|
||||
#send: ATTracker['events']['send']
|
||||
if (typeof window === 'undefined' || !window.pa) {
|
||||
throw new Error('Piano Analytics script not loaded')
|
||||
}
|
||||
|
||||
class PianoTracker implements ATTracker {
|
||||
constructor(options: { language: 'fr' | 'en' }) {
|
||||
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.setConfigurations({
|
||||
site,
|
||||
collectDomain: 'https://tm.urssaf.fr',
|
||||
privacyDefaultMode: doNotTrack ? 'optout' : 'exempt',
|
||||
})
|
||||
|
||||
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
|
||||
window.pa.setProperties(
|
||||
{
|
||||
env_language: options.language,
|
||||
'n:simulateur_embarque': document.location.pathname.includes(
|
||||
'/iframes/'
|
||||
)
|
||||
? 1
|
||||
: 0,
|
||||
},
|
||||
{ persistent: true }
|
||||
)
|
||||
}
|
||||
|
||||
if (import.meta.env.MODE === 'production' && doNotTrack) {
|
||||
this.privacy.setVisitorOptout()
|
||||
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)
|
||||
} else {
|
||||
this.privacy.setVisitorMode('cnil', 'exempt')
|
||||
window.pa.sendEvent(type, data as ClickHit & PageHit)
|
||||
}
|
||||
}
|
||||
|
||||
#currentPageInfo: PageHit = {}
|
||||
consent = {
|
||||
setMode(type: 'exempt' | 'optout'): void {
|
||||
window.pa.consent.setMode(type)
|
||||
},
|
||||
getMode(): { name: 'exempt' | 'optout' } {
|
||||
return window.pa.consent.getMode()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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')
|
||||
}
|
||||
return PianoTracker
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React, { createContext, useContext, useEffect } from 'react'
|
||||
|
||||
import { ATTracker, Log } from './Tracker'
|
||||
import { ATTracker } from './Tracker'
|
||||
|
||||
export const TrackingContext = createContext<ATTracker>(new Log())
|
||||
export const TrackingContext = createContext<ATTracker | null>(null)
|
||||
|
||||
// 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.events.send(
|
||||
tag?.sendEvent(
|
||||
'page.display',
|
||||
Object.fromEntries(
|
||||
Object.entries({
|
||||
|
|
|
@ -66,7 +66,7 @@ export function Feedback({
|
|||
const submitFeedback = useCallback(
|
||||
(rating: FeedbackT) => {
|
||||
setFeedbackGivenForUrl(url)
|
||||
tag.events.send('click.action', {
|
||||
tag?.sendEvent('click.action', {
|
||||
click_chapter1: 'satisfaction',
|
||||
click: rating,
|
||||
})
|
||||
|
|
|
@ -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 } from 'react'
|
||||
import { createContext, ReactNode, useEffect, useState } 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 { createTracker } from './ATInternetTracking/Tracker'
|
||||
import { ATTracker, createTracker } from './ATInternetTracking/Tracker'
|
||||
import { ErrorFallback } from './ErrorPage'
|
||||
import { IframeResizer } from './IframeResizer'
|
||||
import { ServiceWorker } from './ServiceWorker'
|
||||
|
@ -92,28 +92,69 @@ 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>
|
||||
<TrackingContext.Provider
|
||||
value={
|
||||
new ATTracker({
|
||||
language: i18next.language as 'fr' | 'en',
|
||||
})
|
||||
}
|
||||
>
|
||||
<TrackingProvider>
|
||||
<BrowserRouter
|
||||
basename={import.meta.env.MODE === 'production' ? '' : basename}
|
||||
future={{ v7_startTransition: true }}
|
||||
>
|
||||
{children}
|
||||
</BrowserRouter>
|
||||
</TrackingContext.Provider>
|
||||
</TrackingProvider>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ export function ShareSimulationPopup({ url }: { url: string }) {
|
|||
<Button
|
||||
size="XS"
|
||||
onPress={() => {
|
||||
tracker.events.send('click.action', {
|
||||
tracker?.sendEvent('click.action', {
|
||||
click_chapter1: 'feature:partage',
|
||||
click: 'lien copié',
|
||||
})
|
||||
|
|
|
@ -102,7 +102,7 @@ export default function ShareOrSaveSimulationBanner({
|
|||
light
|
||||
size="XS"
|
||||
onPress={(e) => {
|
||||
tracker.events.send('click.action', {
|
||||
tracker?.sendEvent('click.action', {
|
||||
click_chapter1: 'feature:partage',
|
||||
click: 'démarré',
|
||||
})
|
||||
|
|
|
@ -42,15 +42,15 @@ export default function PrivacyPolicy({
|
|||
const handleChange = useCallback(
|
||||
(checked: boolean) => {
|
||||
if (checked) {
|
||||
tracker.privacy.setVisitorOptout()
|
||||
tracker?.consent.setMode('optout')
|
||||
safeLocalStorage.setItem('tracking:do_not_track', '1')
|
||||
} else {
|
||||
tracker.privacy.setVisitorMode('cnil', 'exempt')
|
||||
tracker?.consent.setMode('exempt')
|
||||
safeLocalStorage.setItem('tracking:do_not_track', '0')
|
||||
}
|
||||
setValueChanged(true)
|
||||
},
|
||||
[setValueChanged, tracker.privacy]
|
||||
[setValueChanged, tracker?.consent]
|
||||
)
|
||||
|
||||
return (
|
||||
|
@ -273,9 +273,10 @@ 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 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{' '}
|
||||
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{' '}
|
||||
<StyledLink
|
||||
href="https://www.cnil.fr/fr/solutions-pour-les-cookies-de-mesure-daudience"
|
||||
aria-label={t(
|
||||
|
@ -301,7 +302,7 @@ export default function PrivacyPolicy({
|
|||
<Checkbox
|
||||
name="opt-out mesure audience"
|
||||
onChange={handleChange}
|
||||
defaultSelected={tracker.privacy.getVisitorMode().name === 'optout'}
|
||||
defaultSelected={tracker?.consent.getMode().name === 'optout'}
|
||||
>
|
||||
{t(
|
||||
'privacyPolicy.tracking.optOut.checkboxLabel',
|
||||
|
|
|
@ -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 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>.
|
||||
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>.
|
||||
optOut:
|
||||
checkboxLabel: I do not want to send anonymous data about my use of the platform
|
||||
for audience measurement purposes.
|
||||
|
|
|
@ -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 Matomo, une solution de mesure
|
||||
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
|
||||
<2>recommandations de la CNIL</2>.
|
||||
|
|
|
@ -214,13 +214,9 @@ export default function EndBlock({ fields, missingValues }: EndBlockProps) {
|
|||
href={url}
|
||||
size="XL"
|
||||
onPress={() => {
|
||||
tracker.setProp(
|
||||
'evenement_type',
|
||||
'telechargement',
|
||||
false
|
||||
)
|
||||
tracker.events.send('demarche.document', {
|
||||
tracker?.sendEvent('demarche.document', {
|
||||
click: 'demande_formulaire_a1',
|
||||
evenement_type: 'telechargement',
|
||||
})
|
||||
}}
|
||||
download="demande-mobilité-internationale.pdf"
|
||||
|
|
Loading…
Reference in New Issue