File

src/app/core/session/session-service/session-manager.service.ts

Description

This service handles the user session. This includes an online and offline login and logout. After a successful login, the database for the current user is initialised.

Index

Properties
Methods

Constructor

constructor(remoteAuthService: KeycloakAuthService, localAuthService: LocalAuthService, sessionInfo: SessionSubject, currentUser: CurrentUserSubject, entityMapper: EntityMapperService, loginStateSubject: LoginStateSubject, router: Router, navigator: Navigator, configService: ConfigService, databaseResolver: DatabaseResolverService)
Parameters :
Name Type Optional
remoteAuthService KeycloakAuthService No
localAuthService LocalAuthService No
sessionInfo SessionSubject No
currentUser CurrentUserSubject No
entityMapper EntityMapperService No
loginStateSubject LoginStateSubject No
router Router No
navigator Navigator No
configService ConfigService No
databaseResolver DatabaseResolverService No

Methods

clearRemoteSessionIfNecessary
clearRemoteSessionIfNecessary()
Returns : any
getOfflineUsers
getOfflineUsers()

Get a list of all users that can log in offline

Returns : SessionInfo[]
Async logout
logout()

If online, clear the remote session. If offline, reset the state and forward to login page.

Returns : unknown
offlineLogin
offlineLogin(user: SessionInfo)

Login an offline session without sync.

Parameters :
Name Type Optional
user SessionInfo No
Returns : any
Async remoteLogin
remoteLogin()

Login for a remote session and start the sync. After a user has logged in once online, this user can later also use the app offline. Should only be called if there is an internet connection

Returns : unknown
remoteLoginAvailable
remoteLoginAvailable()
Returns : boolean

Properties

Readonly RESET_REMOTE_SESSION_KEY
Type : string
Default value : "RESET_REMOTE"
import { Inject, Injectable } from "@angular/core";

import { SessionInfo, SessionSubject } from "../auth/session-info";
import { LoginStateSubject, SessionType } from "../session-type";
import { LoginState } from "../session-states/login-state.enum";
import { Router } from "@angular/router";
import { KeycloakAuthService } from "../auth/keycloak/keycloak-auth.service";
import { LocalAuthService } from "../auth/local/local-auth.service";
import { environment } from "../../../../environments/environment";
import { NAVIGATOR_TOKEN } from "../../../utils/di-tokens";
import { CurrentUserSubject } from "../current-user-subject";
import { EntityMapperService } from "../../entity/entity-mapper/entity-mapper.service";
import { filter, take } from "rxjs/operators";
import { Subscription } from "rxjs";
import { Entity } from "../../entity/model/entity";
import { ConfigService } from "../../config/config.service";
import { DatabaseResolverService } from "../../database/database-resolver.service";

/**
 * This service handles the user session.
 * This includes an online and offline login and logout.
 * After a successful login, the database for the current user is initialised.
 */
@Injectable()
export class SessionManagerService {
  readonly RESET_REMOTE_SESSION_KEY = "RESET_REMOTE";
  private remoteLoggedIn = false;
  private updateSubscription: Subscription;

  constructor(
    private remoteAuthService: KeycloakAuthService,
    private localAuthService: LocalAuthService,
    private sessionInfo: SessionSubject,
    private currentUser: CurrentUserSubject,
    private entityMapper: EntityMapperService,
    private loginStateSubject: LoginStateSubject,
    private router: Router,
    @Inject(NAVIGATOR_TOKEN) private navigator: Navigator,
    private configService: ConfigService,
    private databaseResolver: DatabaseResolverService,
  ) {}

  /**
   * Login for a remote session and start the sync.
   * After a user has logged in once online, this user can later also use the app offline.
   * Should only be called if there is an internet connection
   */
  async remoteLogin() {
    this.loginStateSubject.next(LoginState.IN_PROGRESS);
    if (this.remoteLoginAvailable()) {
      return this.remoteAuthService
        .login()
        .then((user) => this.handleRemoteLogin(user))
        .catch((err) => {
          this.loginStateSubject.next(LoginState.LOGIN_FAILED);
          // ignore fall back to offline login - if there was a technical error, the AuthService has already logged/reported it
        });
    }
    this.loginStateSubject.next(LoginState.LOGIN_FAILED);
  }

  remoteLoginAvailable() {
    return navigator.onLine && environment.session_type === SessionType.synced;
  }

  /**
   * Login an offline session without sync.
   * @param user
   */
  offlineLogin(user: SessionInfo) {
    return this.initializeUser(user);
  }

  private async initializeUser(session: SessionInfo) {
    await this.databaseResolver.initDatabasesForSession(session);
    this.sessionInfo.next(session);
    this.loginStateSubject.next(LoginState.LOGGED_IN);
    this.configService.configUpdates.pipe(take(1)).subscribe(() =>
      // requires initial config to be loaded first!
      this.initUserEntity(session.entityId),
    );
  }

  private initUserEntity(entityId: string) {
    if (!entityId) {
      this.currentUser.next(null);
      return;
    }

    const entityType = Entity.extractTypeFromId(entityId);
    this.entityMapper
      .load(entityType, entityId)
      .catch(() => null) // see CurrentUserSubject: emits "null" for non-existing user entity
      .then((res) => this.currentUser.next(res));
    this.updateSubscription = this.entityMapper
      .receiveUpdates(entityType)
      .pipe(
        filter(
          ({ entity }) =>
            entity.getId() === entityId || entity.getId(true) === entityId,
        ),
      )
      .subscribe(({ entity }) => this.currentUser.next(entity));
  }

  /**
   * Get a list of all users that can log in offline
   */
  getOfflineUsers(): SessionInfo[] {
    return this.localAuthService.getStoredUsers();
  }

  /**
   * If online, clear the remote session.
   * If offline, reset the state and forward to login page.
   */
  async logout() {
    if (this.remoteLoggedIn) {
      if (this.navigator.onLine) {
        // This will forward to the keycloak logout page
        await this.remoteAuthService.logout();
      } else {
        localStorage.setItem(this.RESET_REMOTE_SESSION_KEY, "1");
      }
    }
    // resetting app state
    this.sessionInfo.next(undefined);
    this.updateSubscription?.unsubscribe();
    this.currentUser.next(undefined);
    this.loginStateSubject.next(LoginState.LOGGED_OUT);
    this.remoteLoggedIn = false;
    await this.databaseResolver.resetDatabases();
    return this.router.navigate(["/login"], {
      queryParams: { redirect_uri: this.router.routerState.snapshot.url },
    });
  }

  clearRemoteSessionIfNecessary() {
    if (localStorage.getItem(this.RESET_REMOTE_SESSION_KEY)) {
      localStorage.removeItem(this.RESET_REMOTE_SESSION_KEY);
      return this.remoteAuthService.logout();
    }
  }

  private async handleRemoteLogin(user: SessionInfo) {
    this.remoteLoggedIn = true;
    await this.initializeUser(user);
    this.localAuthService.saveUser(user);
  }
}

results matching ""

    No results matching ""