Angular mit Zeitstempel und Version

Selbst bei einfachstem Test- und Release-Vorgehen sollte immer klar sein welche Version der Anwendung man gerade vor sich hat. Hilfreich ist es dabei einen Zeitstempel und/oder die aktuelle Version innerhalb der Anwendung sichtbar zu machen.

Version

Die Version unserer Anwendung finden wir in unser package.json. Diese Datei ist aber natürlich nicht Teil unseres ausgelieferten Bundles. Diese Datei beim build einfach zu kopieren und somit verfügbar zu machen mag einfach sein, offenbart aber potenziellen Angreifern eine Vielzahl von Informationen über unser Projekt und dessen Abhängigkeiten. Stattdessen importieren wir in unserer Komponente die package.json und ermöglichen unserem TypeScript Compiler die Version zu extrahieren:

import {version} from '../../package.json';
...
@Component({})
export class AppComponent {
  version: string;

Damit TypeScript diesen Zugriff auf eine einfache JSON-Datei per Import erlaubt, reicht es aus in unserer tsconfig folgenden Eintrag zu ergänzen:

    "resolveJsonModule": true,

Zeitstempel

Der Zeitstempel stellt tatsächlich eine größere „Herausforderung“ dar. Unser Vorgehen: ein Eintrag in unserer Environment soll soll dazu dienen den Zeitstempel in der Anwendung verfügbar zu machen. Damit lässt sich eine Ausgabe in unserer Komponente ganz einfach realisieren

    this.version = `${version} - ${environment.buildTime}`

Unsere environments bereiten wir mit einem entsprechenden Eintrag vor:

export const environment = {
  buildTime: '',
...

Diese Variable muss aber nun natürlich beim Build auch gesetzt werden. Dazu verwenden wir ein NPM-Tool:

replace-in-file – npm (npmjs.com)

Damit können wir mittels node-Script, einen beliebigen Text-Ersatz innerhalb von Dateien ausführen. Ein entsprechendes Script könnte so aussehen. Dabei ermöglichen wir auch das „zurücksetzen“ auf einen Default Wert. Hintergrund: wir wollen keine unnötigen Dateiänderungen in unserem Versionskontrollsystem nach einem Build:

let replace = require("replace-in-file");
let buildTime = new Date().toISOString();
let revert = process.argv[2];

const options = {
  files: "src/environments/*.ts",
  allowEmptyPaths: false,
  from: /buildTime.*/g,
  to: `buildTime: '${revert ? '' : buildTime}',`
};

try {
  replace.sync(options);
} catch (error) {
  console.error("Error while setting build time:", error);
}

Anpassung an unserem npm-build-script:

"build": "node ./replace.build.js 
           &&  ng build --prod --base-href . 
           && node ./replace.build.js revert",

…und schon landet eine kleine nette Info in unserer Anwendung:

Weihnachten 2020

GEDOPLAN wünscht Frohe Weihnachten 2020

https://gedoplan.github.io/ged-xmas2020/index.html

source: https://github.com/GEDOPLAN/ged-xmas2020

Richfaces und JSF 2.3.X

Jeder der sich gerade die Augen reibt: ja er hat „Richfaces“ geschrieben. Auch wenn Richfaces das Ende seines Lebens schon lange erreicht hat existieren hier und da noch einige Projekte die sich auf die Funktionalität eben dieser Bibliothek verlassen

…trotzdem sollten wir natürlich damit unseren Aktualisierungswunsch aller anderen Komponenten/Bibliotheken nicht zu den Akten legen. Insbesondere ein Update der Core-JSF Bibliothek ist aus Security Sicht stark anzuraten, werden doch immer mal wieder wichtige Sicherheitslücken geschlossen. Das ist in einem Application Server (selbst ohne diesen komplett zu aktualisieren) in aller Regel kein Problem, „javax.faces.jar“ austauschen und fertig. Nun ist (sagen wir „war“?) Richfaces dafür bekannt nicht gerade tolerant mit Versionswechseln jeglicher Art um zu gehen. Ein Blick in die Anwendung lässt auch hier kein gutes Gefühl aufkommen: „irgendwie langsamer“. Und das aus gutem Grund. Wirft man einen Blick in die generierten HTML-Ausgaben finden wir die Antwort:

Richfaces lädt, je nach Seite, gleich dutzendfach seine benötigten Ressourcen und bremst die Anwendung spürbar aus. Dabei wird die packed.js von Richfaces eigentlich dazu verwendet um benötige Ressourcen zu bundeln und performant in nur einem Request abzufragen.

Eine einfache und pragmatische Lösung: die Ressourcen-Optimierung deaktivieren. Dazu reicht ein entsprechender Eintrag in der web.xml:

    <context-param>
        <param-name>org.richfaces.resourceOptimization.enabled</param-name>
        <param-value>true</param-value>
    </context-param>

Zwar generieren wir damit immer noch mehr Requests als es eigentlich sein müssten, da die benötigten Ressourcen nun einzeln abgefragt werden. Dafür passiert das nun aber auch nur noch einmal.

Ende gut. Alles gut. Vielleicht insbesondere bei Richfaces ein passender Schlusssatz 😉 .

Keycloak + Protractor E2E Test

Keycloak ist eine charmante Authentifizierungs-Lösung die sich dank keycloak-angular relativ Problemlos in der eigenen Anwendung verankern lässt. Im Arbeitsalltag stolpert man dann aber in Kombination mit E2E-Tests über das ein oder andere Problem…

Protractor ist cool, kümmert es sich doch normalerweise um all die asyncronen HTTP-Calls und Timer die in unserer Anwendung ablaufen. So wird der Test den wir deklarieren erst dann durchgeführt wenn z.B. die HTTP Abfrage + Rendering auch vollständig durchlaufen ist. Wenn wir Keycloak einsetzen scheitert dieses Vorgehen leider mit einem Timeout:

Failed: Angular could not be found on the page http://localhost:4200/.

If this is not an Angular application, you may need to turn off waiting for Angular.

Recht hat er ja auch irgendwie. Unsere Anwendung wird den User in aller Regel auf die Login-Seite von Keycloak weiter leiten. Was wir nun finden werden sind Ratschläge die Syncronisation zu deaktivieren:

  browser.waitForAngularEnabled(false);

Soweit so gut, nach erfolgreichem Login einfach wieder aktivieren und… nein doch nicht.

Keycloak blockiert bei diesem Vorgehen jegliche weiteren Tests, die somit auf einen Timeout laufen.

Nun könnten wir natürlich die Synchronisation wie oben zu sehen einfach deaktiviert lassen. Das wird uns allerdings nur wenig Glücklich machen, müssen wir doch so an sehr sehr vielen Stellen unnötige Prüfungen und „sleeps“ verwenden, da wir nun selber auf die Abarbeitung der Asyncronen Calls reagieren und/oder warten müssen. Im entsprechenden BugTicket finden wir aber die Idee Keycloak einfach außerhalb von Angular zu initialisieren:

https://github.com/mauriciovigolo/keycloak-angular/issues/73#issuecomment-393967726

Um das bei Angular-Start durch zu führen + in unserem Beispiel sogar noch die Konfiguration für Keycloak von externer Quelle zu ziehen könnte das so aussehen:


const keycloakService = new KeycloakService();

@NgModule({
  ...
  providers: [
    {
      provide: KeycloakService,
      useValue: keycloakService,
    },
    {
      provide: APP_INITIALIZER,
      useFactory: onAppInit,
      deps: [KeycloakConfigControllerService],
      multi: true,
    },
  ]
})
export class AppModule implements DoBootstrap {
  constructor(public ngZone: NgZone) {}

  ngDoBootstrap(appRef: ApplicationRef) {
    this.initWithKeycloak().then(() => appRef.bootstrap(AppComponent));
  }

  initWithKeycloak() {
    return new Promise((resolve) => {
      this.ngZone.runOutsideAngular(() => {
        keycloakService
          .init({
          // normal init here
          })
          .then(() => {
            resolve();
          })
      });
    });
  }
}

export function onAppInit(kcConfig: KeycloakConfigControllerService) {
  return () =>
    new Promise<any>((resolve) => {
      kcConfig.getKeycloakConfig().subscribe((r) => {
        Object.assign(config, r);
        resolve();
      });
    });
}