Как транслировать угловой ответ http нескольким наблюдателям - PullRequest
0 голосов
/ 15 октября 2019

Когда я делаю запрос авторизации к своему API (войдите, обновите токен доступа), я хотел бы передать результат нескольким наблюдателям.

Наблюдатели - это либо компоненты (я показываю кнопку выхода из системы), например), или перехватчик http (при повторной попытке запроса после 401).

Мой текущий метод состоит в том, чтобы выдавать обновленные токены от субъектов в моей службе аутентификации. Компоненты наблюдают за субъектами, а мой компонент входа в систему и http-перехватчик делают запросы на вход в систему и обновление токена.

Проблема в том, что обновление токена находится в середине наблюдаемого потока внутри http-перехватчика, которыйУмирает, когда я вызываю subject.next (токен) из моей службы аутентификации.

Компонент заголовка:

export class HeaderComponent {
 loggedIn: boolean;

  ngOnInit() {
    this.authenticationService
      .isLoggedIn()
      .pipe(takeUntil(this.destroyed))
      .subscribe((loggedIn: boolean) => {
        this.loggedIn = loggedIn;
        this.detectChanges();
      });
  }
}

Компонент входа:

export class LoginComponent {
  onFormSubmit(credentials): void {
    this.authenticationService
      .login(credentials)
      .subscribe((token: string) => {
        if (token) {
          // navigate away from login page
        }
      });
  }
}

HttpInterceptor:

export class HttpAuthInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    request = this.setAuthenticationHeader(request);

    return next.handle(request)
      .pipe(
        catchError((err: any) => {
          // assume HttpErrorResponse for brevity
          if (err.status !== 401) {
            return throwError(err);
          }

          return this.authenticationService
           .refreshToken()
           .pipe(
             // a token request has been made, but the stream has now died
             tap(() => console.log('I am not called')),
             switchMap(token => {
               request = this.setAuthenticationHeader(request);
               return next.handle(request);
             }),
           );
        })
      );
  }
}

Упрощенная версия моей текущей службы аутентификации:

export class AuthenticationService {
  constructor(private http: HttpClient) {
    this.tokenSubject = new BehaviorSubject<string>(this.token);
  }

  private refreshToken: string; // from storage in reality
  private token: string; // from storage in reality
  private tokenSubject: Subject<string>;

  isLoggedIn(): Observable<boolean> {
    return this.tokenSubject
      .pipe(
        map(token => !!token)
      );
  }

  login(credentials): Observable<string> {
    return this.http
      .post('https://myapi/token', mycredentials, myloginheaders)
      .pipe(
        map(response => {
          this.setToken(response.access_token, response.refresh_token);
          return this.token;
        }),
        catchError(err => {
          this.setToken(null);
          return of(this.token);
        })
      );
  }

  refreshToken(): Observable<string> {
    return this.http
      .post('https://myapi/token', mytoken, myrefreshheaders)
      .pipe(
        map(response => {
          this.setToken(response.access_token, response.refresh_token);
          return this.token;
        }),
        catchError(err => {
          this.setToken(null);
          return of(this.token);
        })
      );
  }

  refreshTokenAttempt1(): Observable<string> {
    const request = this.http
      .post('https://myapi/token', mytoken, myrefreshheaders)
      .pipe(
        map(response => {
          this.setToken(response.access_token, response.refresh_token);
          return this.token;
        }),
        catchError(err => {
          this.setToken(null);
          return of(this.token);
        }),
        multicast(this.tokenSubject)
      );

    // Add multicast into the pipeline and call connect.
    // My guess is that calling connect from here AND subscribe from the client is not good
    (request as ConnectableObservable<string>).connect();

    return request;
  }

  refreshTokenAttempt2(): Observable<string> {
    const request = this.http
      .post('https://myapi/token', mytoken, myrefreshheaders)
      .pipe(
        map(response => {
          this.setToken(response.access_token, response.refresh_token);
          return this.token;
        }),
        catchError(err => {
          this.setToken(null);
          return of(this.token);
        }),
        multicast(this.tokenSubject),
        // Add publish to the pipeline in an attempt to push the result into the token subject. Not working - because it's still cold?
        publish()
      );

    return request;
  }

  private setToken(token: string, refreshToken: string): void {
    this.refreshToken = refreshToken;
    this.token = token;
    // this seems to kill any observable stream that we are currently inside
    this.tokenSubject.next(token);
  }
}

Мне кажется, что мне нужно как-то передать результат запроса токена в мою тему токена в службе аутентификации.

Я думаю, что мне нужна многоадресная рассылка, но я не могу найти примеров трансляции результата одноразового наблюдаемого события (например, http-запроса) на субъектов с активными наблюдателями (например, моего токена).

1 Ответ

0 голосов
/ 15 октября 2019

Я придумала решение моей конкретной проблемы, хотя оно не является ответом на более общий вопрос.

Вместо того, чтобы пытаться передавать обновленное значение через предмет токена изнутри наблюдаемой,Я делаю http-запрос горячим внутри службы и удовлетворяю внешние вызовы только темой.

Служба аутентификации:

export class AuthenticationService {
  constructor(private http: HttpClient) {
    this.token = null; // from storage in reality
    this.tokenSubject = new BehaviorSubject<string>(this.token);

    this.loginSubject = new Subject<string>();
    this.refreshSubject = new Subject<string>();
  }

  private token: {
    accessToken: string,
    refreshToken: string
  };
  private loginSubject: Subject<string>;
  private refreshSubject: Subject<string>;
  private tokenSubject: Subject<string>;

  isLoggedIn(): Observable<boolean> {
    return this.tokenSubject
      .pipe(map(token => !!token));
  }

  login(credentials): Observable<string> {
     const loginParams = {}; /*convert credentials to http params*/
     this.requestToken(loginParams, loginHeaders, this.loginSubject);
     return this.tokenSubject.asObservable();
  }

  refreshToken(): Observable<string> {
     const refreshParams = {}; /*convert credentials to http params*/
     this.requestToken(refreshParams, requestHeaders, this.refreshSubject);
     return this.tokenSubject.asObservable();
  }

  private requestToken(params, headers, subject: Subject<string>): void {
    this.http
      .post('https://myapi/token', params, { headers })
      .pipe(
        map(response => ({
          accessToken: response.access_token,
          refreshToken: response.refresh_token
        }),
        catchError(err => of(null))
      )
      .subscribe(token => {
        this.token = token;
        this.tokenSubject.next(!!token ? token.accessToken : null);
        subject.next(!!token ? token.accessToken : null);
      });
  }
}

Недостатком этого является то, что у меня нет средств для завершениясубъекты, но на практике всегда есть активные наблюдатели, поэтому это не должно иметь значения.

...