diff --git a/src/app/app.component.ts b/src/app/app.component.ts index d576780d59..e3b7149740 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -31,7 +31,8 @@ import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider'; import { environment } from '../environments/environment'; import { models } from './core/core.module'; import { LocaleService } from './core/locale/locale.service'; -import { CookiesService } from './shared/cookies/cookies.service'; +import { KlaroService } from './shared/cookies/klaro.service'; +import { hasValue } from './shared/empty.util'; @Component({ selector: 'ds-app', @@ -69,7 +70,7 @@ export class AppComponent implements OnInit, AfterViewInit { private menuService: MenuService, private windowService: HostWindowService, private localeService: LocaleService, - @Optional() private cookiesService: CookiesService + @Optional() private cookiesService: KlaroService ) { /* Use models object so all decorators are actually called */ @@ -83,7 +84,7 @@ export class AppComponent implements OnInit, AfterViewInit { // set the current language code this.localeService.setCurrentLanguageCode(); - this.cookiesService.initialize(); + this.initializeKlaro(); angulartics2GoogleAnalytics.startTracking(); angulartics2DSpace.startTracking(); @@ -163,4 +164,9 @@ export class AppComponent implements OnInit, AfterViewInit { ); } + private initializeKlaro() { + if (hasValue(this.cookiesService)) { + this.cookiesService.initialize() + } + } } diff --git a/src/app/core/locale/locale.service.ts b/src/app/core/locale/locale.service.ts index b7f9314a33..32f782ef01 100644 --- a/src/app/core/locale/locale.service.ts +++ b/src/app/core/locale/locale.service.ts @@ -10,7 +10,7 @@ import { Observable, of as observableOf, combineLatest } from 'rxjs'; import { map, take, flatMap } from 'rxjs/operators'; import { NativeWindowService, NativeWindowRef } from '../services/window.service'; -export const LANG_COOKIE = 'language_cookie'; +export const LANG_COOKIE = 'dsLanguage'; /** * This enum defines the possible origin of the languages diff --git a/src/app/core/shared/dspace-object.model.ts b/src/app/core/shared/dspace-object.model.ts index a9256fbb7f..3abb9bceed 100644 --- a/src/app/core/shared/dspace-object.model.ts +++ b/src/app/core/shared/dspace-object.model.ts @@ -184,4 +184,25 @@ export class DSpaceObject extends ListableObject implements CacheableObject { getRenderTypes(): Array> { return [this.constructor as GenericConstructor]; } + + setMetadata(key: string, language?: string, ...values: string[]) { + const mdValues: MetadataValue[] = values.map((value: string, index: number) => { + const md = new MetadataValue(); + md.value = value; + md.authority = null; + md.confidence = -1; + md.language = language || null; + md.place = index; + return md; + }); + if (hasNoValue(this.metadata)) { + this.metadata = Object.create({}); + } + this.metadata[key] = mdValues; + } + + removeMetadata(key: string) { + delete this.metadata[key]; + } + } diff --git a/src/app/footer/footer.component.ts b/src/app/footer/footer.component.ts index da71ac635d..ef11093c7b 100644 --- a/src/app/footer/footer.component.ts +++ b/src/app/footer/footer.component.ts @@ -1,5 +1,6 @@ -import { Component } from '@angular/core'; -import { CookiesService } from '../shared/cookies/cookies.service'; +import { Component, Optional } from '@angular/core'; +import { KlaroService } from '../shared/cookies/klaro.service'; +import { hasValue } from '../shared/empty.util'; @Component({ selector: 'ds-footer', @@ -7,14 +8,15 @@ import { CookiesService } from '../shared/cookies/cookies.service'; templateUrl: 'footer.component.html' }) export class FooterComponent { - constructor(private cookies: CookiesService) { - - } - dateObj: number = Date.now(); + constructor(@Optional() private cookies: KlaroService) { + } + showCookieSettings() { - this.cookies.showSettings(); + if (hasValue(this.cookies)) { + this.cookies.showSettings(); + } return false; } } diff --git a/src/app/shared/cookies/cookies.service.spec.ts b/src/app/shared/cookies/klaro.service.spec.ts similarity index 55% rename from src/app/shared/cookies/cookies.service.spec.ts rename to src/app/shared/cookies/klaro.service.spec.ts index aebf8ab956..0c1f5b368e 100644 --- a/src/app/shared/cookies/cookies.service.spec.ts +++ b/src/app/shared/cookies/klaro.service.spec.ts @@ -1,12 +1,12 @@ import { TestBed } from '@angular/core/testing'; -import { CookiesService } from './cookies.service'; +import { KlaroService } from './klaro.service'; -describe('CookiesService', () => { +describe('KlaroService', () => { beforeEach(() => TestBed.configureTestingModule({})); it('should be created', () => { - const service: CookiesService = TestBed.get(CookiesService); + const service: KlaroService = TestBed.get(KlaroService); expect(service).toBeTruthy(); }); }); diff --git a/src/app/shared/cookies/cookies.service.ts b/src/app/shared/cookies/klaro.service.ts similarity index 75% rename from src/app/shared/cookies/cookies.service.ts rename to src/app/shared/cookies/klaro.service.ts index cfed63420e..ffae51969c 100644 --- a/src/app/shared/cookies/cookies.service.ts +++ b/src/app/shared/cookies/klaro.service.ts @@ -1,26 +1,23 @@ import { Injectable } from '@angular/core'; import * as Klaro from 'klaro' -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, Observable } from 'rxjs'; import { TOKENITEM } from '../../core/auth/models/auth-token-info.model'; import { AuthService, IMPERSONATING_COOKIE, REDIRECT_COOKIE } from '../../core/auth/auth.service'; import { LANG_COOKIE } from '../../core/locale/locale.service'; import { TranslateService } from '@ngx-translate/core'; import { environment } from '../../../environments/environment'; -import { take } from 'rxjs/operators'; +import { map, switchMap, take } from 'rxjs/operators'; import { EPerson } from '../../core/eperson/models/eperson.model'; +import { of as observableOf, combineLatest as observableCombineLatest } from 'rxjs'; -export const HAS_AGREED_END_USER = 'hasAgreedEndUser'; -export const KLARO = 'klaro'; - +export const HAS_AGREED_END_USER = 'dsHasAgreedEndUser'; +export const COOKIE_MDFIELD = 'dspace.agreements.cookies'; const cookieNameMessagePrefix = 'cookies.consent.app.title.'; const cookieDescriptionMessagePrefix = 'cookies.consent.app.description.'; const cookiePurposeMessagePrefix = 'cookies.consent.purpose.'; -@Injectable({ - providedIn: 'root' -}) - -export class CookiesService { +@Injectable() +export class KlaroService { message$: BehaviorSubject = new BehaviorSubject(''); @@ -129,7 +126,7 @@ export class CookiesService { purposes: ['acknowledgement'], required: true, cookies: [ - KLARO + [/^klaro-.+$/], ] }, { @@ -183,6 +180,7 @@ export class CookiesService { passed as the second parameter. */ callback: (consent, app) => { + console.log(consent, app); this.message$.next('User consent for app ' + app.name + ': consent=' + consent); }, /* @@ -193,41 +191,72 @@ export class CookiesService { */ onlyOnce: true, }, - ] + ], + /* + You can define an optional callback function that will be called each time the + consent state for any given app changes. The consent value will be passed as the + first parameter to the function (true=consented). The `app` config will be + passed as the second parameter. + */ + callback: (consent, app) => { + + /* + You can define an optional callback function that will be called each time the + consent state for any given app changes. The consent value will be passed as the + first parameter to the function (true=consented). The `app` config will be + passed as the second parameter. + */ + console.log( + 'User consent for app ' + app.name + ': consent=' + consent + ); + // this.updateUserCookieConsent(); + }, }; constructor( private translateService: TranslateService, - private authService: AuthService, - ) { + private authService: AuthService) { } initialize() { - this.authService.getAuthenticatedUserFromStore() - .subscribe((user: EPerson) => { - this.klaroConfig.storageName = 'klaro-' + (user.uuid); - - }); - /** - * Add all message keys for apps and purposes - */ - this.addAppMessages(); /** * Make sure the fallback language is english */ this.translateService.setDefaultLang(environment.defaultLanguage); - /** - * Subscribe on a message to make sure the translation service is ready - * Translate all keys in the translation section of the configuration - * Show the configuration if the configuration has not been confirmed - */ - this.translateService.get('loading.default').pipe(take(1)).subscribe(() => { - this.translateConfiguration(); - Klaro.renderKlaro(this.klaroConfig, false); - Klaro.initialize(); - }) + const storageName$: Observable = this.authService.isAuthenticated() + .pipe( + take(1), + switchMap((loggedIn: boolean) => { + if (loggedIn) { + return this.authService.getAuthenticatedUserFromStore().pipe(map((user: EPerson) => 'klaro-' + user.uuid), take(1)) + } else { + return observableOf('klaro-anonymous') + } + }) + ); + const translationServiceReady$ = this.translateService.get('loading.default').pipe(take(1)); + + observableCombineLatest(storageName$, translationServiceReady$) + .subscribe(([name, translation]: string[]) => { + this.klaroConfig.storageName = name; + + /** + * Add all message keys for apps and purposes + */ + this.addAppMessages(); + + /** + * Subscribe on a message to make sure the translation service is ready + * Translate all keys in the translation section of the configuration + * Show the configuration if the configuration has not been confirmed + */ + this.translateConfiguration(); + Klaro.renderKlaro(this.klaroConfig, false); + Klaro.initialize(); + }); + } private getTitleTranslation(title: string) { @@ -277,4 +306,12 @@ export class CookiesService { }); return object; } + + getSettingsForUser(user: EPerson) { + return JSON.parse(user.firstMetadataValue(COOKIE_MDFIELD)); + } + + setSettingsForUser(user: EPerson, config: object) { + return user.setMetadata(COOKIE_MDFIELD, undefined, JSON.stringify(config)); + } } diff --git a/src/modules/app/browser-app.module.ts b/src/modules/app/browser-app.module.ts index be4402fc74..e00f998ba6 100644 --- a/src/modules/app/browser-app.module.ts +++ b/src/modules/app/browser-app.module.ts @@ -21,9 +21,9 @@ import { AuthService } from '../../app/core/auth/auth.service'; import { Angulartics2RouterlessModule } from 'angulartics2/routerlessmodule'; import { SubmissionService } from '../../app/submission/submission.service'; import { StatisticsModule } from '../../app/statistics/statistics.module'; +import { KlaroService } from '../../app/shared/cookies/klaro.service'; import { HardRedirectService } from '../../app/core/services/hard-redirect.service'; import { BrowserHardRedirectService } from '../../app/core/services/browser-hard-redirect.service'; -import { CookiesService } from '../../app/shared/cookies/cookies.service'; export const REQ_KEY = makeStateKey('req'); @@ -85,8 +85,8 @@ export function locationProvider(): Location { useClass: ClientCookieService }, { - provide: CookiesService, - useClass: CookiesService + provide: KlaroService, + useClass: KlaroService }, { provide: SubmissionService, diff --git a/tsconfig.server.json b/tsconfig.server.json index 1329b32ace..2d23226548 100644 --- a/tsconfig.server.json +++ b/tsconfig.server.json @@ -6,5 +6,15 @@ }, "angularCompilerOptions": { "entryModule": "./src/modules/app/server-app.module#ServerAppModule" - } + }, + "exclude": [ + "src/test.ts", + "src/**/*.spec.ts", + "src/**/*.mock.ts", + "src/**/*.test.ts", + "src/**/*.stub.ts", + "src/**/testing/*", + "src/**/mocks/*", + "node_modules/klaro" + ], }