diff --git a/CHANGELOG.md b/CHANGELOG.md index f1864a5e..093eeb6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,14 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline ## next-release (YYYY-MM-DD) +### Client +#### Bug fixes +* **(statistics)** : suppression de l'utilisation du DeviceId pour le suivi des statistiques d'usage comme préconisé dans le RGPD. Utilisation d'une UID générée automatiquement par le client à la place. + ### Backend #### Bug fixes * **(maps)** : suppression du tracking du fichier map-data.json et ajout d'un fichier .dist avec des POI factices +* **(statistics)** : prise en compte de la nouvelle uid de tracking à la place du Device Id pour la génération des statistiques d'usage ## 1.1.0 (2024-09-13) diff --git a/dev/user-backend-nest/microservices/statistics/src/statistics/statistics.dto.ts b/dev/user-backend-nest/microservices/statistics/src/statistics/statistics.dto.ts index 89a3c30e..0fb8da1e 100644 --- a/dev/user-backend-nest/microservices/statistics/src/statistics/statistics.dto.ts +++ b/dev/user-backend-nest/microservices/statistics/src/statistics/statistics.dto.ts @@ -38,7 +38,7 @@ */ export interface StatisticsUserActionDto { - uid: string | null; + uid: string; userAgent: string; xForwardedFor: string; duid: string; @@ -49,8 +49,8 @@ export interface StatisticsUserActionDto { } export interface StatisticsExternalApiUserActionDto { - uid: string | null; - duid: string | null; + uid: string; + duid: string; action: string; service: string; platform: string | null; diff --git a/dev/user-backend-nest/microservices/statistics/src/statistics/statistics.service.ts b/dev/user-backend-nest/microservices/statistics/src/statistics/statistics.service.ts index f3e3fcf9..8f2c9803 100644 --- a/dev/user-backend-nest/microservices/statistics/src/statistics/statistics.service.ts +++ b/dev/user-backend-nest/microservices/statistics/src/statistics/statistics.service.ts @@ -83,7 +83,7 @@ export class StatisticsService { const requestData: StatisticsExternalApiUserActionDto = { uid: statData.uid, - duid: statData.platform != 'web' ? statData.duid : null, + duid: statData.duid && statData.duid !== '' ? statData.duid : 'unknown', action: mappedAction, service: statData.functionality, platform: statData.platform, diff --git a/dev/user-frontend-ionic/projects/shared/src/lib/statistics/statistics.repository.ts b/dev/user-frontend-ionic/projects/shared/src/lib/statistics/statistics.repository.ts new file mode 100644 index 00000000..a197ec8f --- /dev/null +++ b/dev/user-frontend-ionic/projects/shared/src/lib/statistics/statistics.repository.ts @@ -0,0 +1,71 @@ +/* + * Copyright ou © ou Copr. Université de Lorraine, (2022) + * + * Direction du Numérique de l'Université de Lorraine - SIED + * (dn-mobile-dev@univ-lorraine.fr) + * JNESIS (contact@jnesis.com) + * + * Ce logiciel est un programme informatique servant à rendre accessible + * sur mobile divers services universitaires aux étudiants et aux personnels + * de l'université. + * + * Ce logiciel est régi par la licence CeCILL 2.1, soumise au droit français + * et respectant les principes de diffusion des logiciels libres. Vous pouvez + * utiliser, modifier et/ou redistribuer ce programme sous les conditions + * de la licence CeCILL telle que diffusée par le CEA, le CNRS et INRIA + * sur le site "http://cecill.info". + * + * En contrepartie de l'accessibilité au code source et des droits de copie, + * de modification et de redistribution accordés par cette licence, il n'est + * offert aux utilisateurs qu'une garantie limitée. Pour les mêmes raisons, + * seule une responsabilité restreinte pèse sur l'auteur du programme, le + * titulaire des droits patrimoniaux et les concédants successifs. + * + * À cet égard, l'attention de l'utilisateur est attirée sur les risques + * associés au chargement, à l'utilisation, à la modification et/ou au + * développement et à la reproduction du logiciel par l'utilisateur étant + * donné sa spécificité de logiciel libre, qui peut le rendre complexe à + * manipuler et qui le réserve donc à des développeurs et des professionnels + * avertis possédant des connaissances informatiques approfondies. Les + * utilisateurs sont donc invités à charger et à tester l'adéquation du + * logiciel à leurs besoins dans des conditions permettant d'assurer la + * sécurité de leurs systèmes et/ou de leurs données et, plus généralement, + * à l'utiliser et à l'exploiter dans les mêmes conditions de sécurité. + * + * Le fait que vous puissiez accéder à cet en-tête signifie que vous avez + * pris connaissance de la licence CeCILL 2.1, et que vous en avez accepté les + * termes. + */ + +import { createStore, select, withProps } from '@ngneat/elf'; +import { + persistState, + localStorageStrategy +} from '@ngneat/elf-persist-state'; + +const STORE_NAME = 'stats-uid'; + +interface StatsUidProps { + uid: string; +} + +const statsUidStore = createStore( + { name: STORE_NAME }, + withProps({ uid: null }) +); + +export const persistStatsUid = persistState(statsUidStore, { + key: STORE_NAME, + storage: localStorageStrategy, +}); + +export const statsUid$ = statsUidStore.pipe(select((state) => state.uid)); + +export const updateStatsUid = (uid: StatsUidProps['uid']) => { + statsUidStore.update((state) => ({ + ...state, + uid, + })); +}; + +export const clearStatsUid = () => statsUidStore.reset(); diff --git a/dev/user-frontend-ionic/projects/shared/src/lib/statistics/statistics.service.ts b/dev/user-frontend-ionic/projects/shared/src/lib/statistics/statistics.service.ts index a74f2962..ab005fa4 100644 --- a/dev/user-frontend-ionic/projects/shared/src/lib/statistics/statistics.service.ts +++ b/dev/user-frontend-ionic/projects/shared/src/lib/statistics/statistics.service.ts @@ -39,12 +39,12 @@ import { HttpClient } from '@angular/common/http'; import { Inject, Injectable } from '@angular/core'; -import { Device } from '@capacitor/device'; import { combineLatest, from, Observable, of } from 'rxjs'; import { catchError, switchMap, take } from 'rxjs/operators'; import { getAuthToken } from '../auth/auth.repository'; import { NetworkService } from '../network/network.service'; import { Capacitor } from '@capacitor/core'; +import { statsUid$, updateStatsUid } from './statistics.repository'; interface UserActionRequestData { authToken: string; @@ -91,13 +91,13 @@ export class StatisticsService { private postUserActionStatistic(userActionDetails: UserActionDetails): Observable { const url = `${this.environment.apiEndpoint}/statistics/user-action`; - return combineLatest([getAuthToken(), from(Device.getId()), from(this.networkService.getConnectionStatus())]).pipe( + return combineLatest([getAuthToken(), statsUid$, from(this.networkService.getConnectionStatus())]).pipe( take(1), - switchMap(([authToken, deviceId, connectionStatus]) => { + switchMap(([authToken, statsUid, connectionStatus]) => { const data: UserActionRequestData = { authToken, data: { - duid: deviceId.identifier, + duid: statsUid, action: userActionDetails.action, functionality: userActionDetails.functionality, platform: Capacitor.getPlatform(), @@ -110,4 +110,24 @@ export class StatisticsService { catchError(() => of(null)) ); } + + // Generate a unique id for stats usage and store it in Local Storage + public async checkAndGenerateStatsUid() { + statsUid$.pipe(take(1)).subscribe((uid) => { + if (!uid) { + updateStatsUid(this.uuid4()); + } + }); + } + + private uuid4() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace( + /[xy]/g, + function (c) { + const r = (Math.random() * 16) | 0, + v = c === 'x' ? r : (r & 0x3) | 0x8; + return v.toString(16); + }, + ); + } } diff --git a/dev/user-frontend-ionic/src/app/app.component.ts b/dev/user-frontend-ionic/src/app/app.component.ts index 16a03daf..42360d37 100644 --- a/dev/user-frontend-ionic/src/app/app.component.ts +++ b/dev/user-frontend-ionic/src/app/app.component.ts @@ -50,8 +50,8 @@ import { ModalController, Platform, PopoverController } from '@ionic/angular'; import { TranslateService } from '@ngx-translate/core'; import { currentLanguage$, features$, FeaturesService, isDarkTheme$, isFeatureStoreInitialized$, NavigationService, - NotificationsService, NetworkService, PageLayout, PageLayoutService, setIsDarkTheme, themeRepoInitialized$, - userHadSetThemeInApp, userHadSetThemeInApp$ + NotificationsService, NetworkService, PageLayout, PageLayoutService, setIsDarkTheme, StatisticsService, + themeRepoInitialized$, userHadSetThemeInApp, userHadSetThemeInApp$ } from '@multi/shared'; import { initializeApp } from 'firebase/app'; import { combineLatest, Observable, of, Subscription } from 'rxjs'; @@ -88,6 +88,7 @@ export class AppComponent implements OnInit, OnDestroy { private networkService: NetworkService, private featuresService: FeaturesService, private notificationsService: NotificationsService, + private statisticsService: StatisticsService, private titleService: Title ) { currentLanguage$.subscribe((language) => { @@ -143,6 +144,8 @@ export class AppComponent implements OnInit, OnDestroy { StatusBar.setStyle({ style: Style.Dark }); }); + + this.statisticsService.checkAndGenerateStatsUid(); } ngOnInit() {