diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index eb23a85..ce6d928 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -73,6 +73,14 @@ spec: # Optional: config without it means no banner, not a failed pod optional: true + - name: MATOMO_MODE + valueFrom: + configMapKeyRef: + name: {{ .Release.Name }}-config + key: matomoMode + # Optional: config without it means direct tracking, not a failed pod + optional: true + ports: - containerPort: 80 imagePullPolicy: Always diff --git a/chart/values.yaml b/chart/values.yaml index 945616c..18995b7 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -14,4 +14,6 @@ config: frontendOnly: "false" environment: local prodUrl: https://test.platform.moleculemaker.org + # 'proxied': Matomo hits go through this app's nginx, without cookies. Anything else: direct. + matomoMode: direct extraDeploy: [] diff --git a/entrypoint.sh b/entrypoint.sh index fdcecaf..0a92f72 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,3 +1,30 @@ #!/bin/bash +# With MATOMO_MODE=proxied, serve the Matomo tracker from this app's own origin, at paths +# ad blockers don't recognize: no "matomo" or "piwik" in the path, and no .php extension +# (EasyPrivacy blocks *.php?action_name=). Hits keep the visitor's IP in X-Forwarded-For. +mkdir -p /etc/nginx/matomo-proxy +rm -f /etc/nginx/matomo-proxy/*.conf +if [ "$MATOMO_MODE" = "proxied" ]; then + upstream="${MATOMO_UPSTREAM:-http://matomo.matomo.svc.cluster.local}" + cat > /etc/nginx/matomo-proxy/locations.conf < /usr/share/nginx/html/assets/config/envvars.json && exec nginx -g 'daemon off;' diff --git a/nginx/default.conf b/nginx/default.conf index a7ab02f..40d6147 100644 --- a/nginx/default.conf +++ b/nginx/default.conf @@ -48,6 +48,10 @@ server { #error_page 404 /404.html; + # Matomo proxy locations, written by entrypoint.sh only when MATOMO_MODE=proxied. + # nginx won't start if a proxy_pass host doesn't resolve, so they can't live here. + include /etc/nginx/matomo-proxy/*.conf; + # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; diff --git a/src/app/models/envvars.ts b/src/app/models/envvars.ts index 3296c9a..abab364 100644 --- a/src/app/models/envvars.ts +++ b/src/app/models/envvars.ts @@ -13,4 +13,8 @@ export interface EnvVars { environment: string; // The released tool, linked from that banner. Empty for local development. prodUrl: string; + + // 'proxied' sends Matomo hits through this app's nginx without cookies; anything else + // (including empty) sends them straight to matomo.mmli2.ncsa.illinois.edu. + matomoMode: string; } diff --git a/src/app/testing/environment-service.stub.ts b/src/app/testing/environment-service.stub.ts index af0e84b..92f513c 100644 --- a/src/app/testing/environment-service.stub.ts +++ b/src/app/testing/environment-service.stub.ts @@ -15,6 +15,7 @@ const envConfig: EnvVars = { frontendOnly: 'true', environment: 'prod', // no staging banner in other components' specs prodUrl: 'http://environment-service.stub', + matomoMode: 'direct', }; export function provideEnvironmentServiceStub() { diff --git a/src/assets/config/envvars.json b/src/assets/config/envvars.json index 4f214c8..749c290 100644 --- a/src/assets/config/envvars.json +++ b/src/assets/config/envvars.json @@ -8,5 +8,7 @@ "frontendOnly": "false", "environment": "local", - "prodUrl": "" + "prodUrl": "", + + "matomoMode": "direct" } diff --git a/src/assets/config/envvars.tpl.json b/src/assets/config/envvars.tpl.json index 028f4b8..5016240 100644 --- a/src/assets/config/envvars.tpl.json +++ b/src/assets/config/envvars.tpl.json @@ -8,6 +8,8 @@ "frontendOnly": "${FRONTEND_ONLY}", "environment": "${ENVIRONMENT}", - "prodUrl": "${PROD_URL}" + "prodUrl": "${PROD_URL}", + + "matomoMode": "${MATOMO_MODE}" } diff --git a/src/main.ts b/src/main.ts index b6e0f0e..9a0760d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,7 +3,7 @@ import { importProvidersFrom, inject, provideAppInitializer } from '@angular/cor import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; import { bootstrapApplication } from '@angular/platform-browser'; import { ReactiveFormsModule } from '@angular/forms'; -import { MatomoModule, MatomoRouterModule } from 'ngx-matomo-client'; +import { MatomoInitializerService, MatomoModule, MatomoRouterModule, MatomoTracker } from 'ngx-matomo-client'; import { MenuModule } from 'primeng/menu'; import { ApiModule, Configuration } from '@api/mmli-backend/v1'; import { DialogModule } from 'primeng/dialog'; @@ -15,10 +15,6 @@ import { APP_IDENTITY } from './app/app-identity'; import { definePreset } from '@primeuix/themes'; import Aura from '@primeng/themes/aura'; -const initAppFn = (envService: EnvironmentService) => { - return () => envService.loadEnvConfig("/assets/config/envvars.json"); -}; - const MyPreset = definePreset(Aura, { semantic: { colorScheme: { @@ -38,10 +34,8 @@ const MyPreset = definePreset(Aura, { bootstrapApplication(AppComponent, { providers: [ importProvidersFrom( - MatomoModule.forRoot({ - siteId: APP_IDENTITY.matomoSiteId, - trackerUrl: 'https://matomo.mmli2.ncsa.illinois.edu/' - }), + // configured once envvars.json has loaded, below + MatomoModule.forRoot({ mode: 'deferred' }), MatomoRouterModule.forRoot(), MenuModule, ApiModule.forRoot(() => new Configuration()), @@ -50,9 +44,28 @@ bootstrapApplication(AppComponent, { AppRoutingModule, ), EnvironmentService, - provideAppInitializer(() => { - const initializerFn = (initAppFn)(inject(EnvironmentService)); - return initializerFn(); + provideAppInitializer(async () => { + const envService = inject(EnvironmentService); + const matomo = inject(MatomoInitializerService); + const tracker = inject(MatomoTracker); + await envService.loadEnvConfig("/assets/config/envvars.json"); + + if (envService.getEnvConfig().matomoMode === 'proxied') { + // Through this app's own nginx (locations written by entrypoint.sh), which ad + // blockers don't recognize, and without cookies, so no consent banner is needed. + tracker.disableCookies(); + matomo.initializeTracker({ + siteId: APP_IDENTITY.matomoSiteId, + trackerUrl: '/px/', + trackerUrlSuffix: 'h', + scriptUrl: '/px/s.js', + }); + } else { + matomo.initializeTracker({ + siteId: APP_IDENTITY.matomoSiteId, + trackerUrl: 'https://matomo.mmli2.ncsa.illinois.edu/', + }); + } }), provideHttpClient(withInterceptorsFromDi()), provideStore(),