Angular 6 - 如何在服务中使用 Observable 来等待 Okta 令牌存在于 localstorage 中

Angular 6 - How to use an Observable in a service to wait until Okta token exists in localstorage

提问人:kriskanya 提问时间:8/3/2018 最后编辑:kriskanya 更新时间:10/10/2018 访问量:1144

问:

在我的应用程序中,我使用第三方身份验证来登录用户,然后在他的本地存储中设置令牌。我正在编写一个服务来缓存配置文件信息,该服务采用该用户的身份验证令牌并调用后端方法以返回用户配置文件信息。getUser()

问题在于,在 localstorage 中设置令牌的时间与应用在初始化时依赖令牌进行后端调用的时间之间存在轻微的延迟。

export class UserService {
  private userProfileSubject = new BehaviorSubject<Enduser>(new Enduser());
  userProfile$ = this.userProfileSubject.asObservable();

  constructor(
    private _adService: AdService,
    private _authService: AuthnService) { }

  setUserProfile() {
    const username = this._authService.getUser();
    this.userProfile$ = this._adService.getUser(username).pipe( 
      first(),
      map(result => result[0]),
      publishReplay(1),
      refCount()
    );
    return this.userProfile$;
  }
}

这是检查 localstorage 令牌并返回用户名的同步方法。

public getUser(): string {
    const jwtHelper = new JwtHelperService()

    const token = localStorage.getItem(environment.JWT_TOKEN_NAME);
    if (!token || jwtHelper.isTokenExpired(token)) {
      return null;
    } else {
      const t = jwtHelper.decodeToken(token);
      return t.username;
    }
  }

所以需要先完成,然后我才能使用它。this._authService.getUser();this._adService.getUser(username)

我认为这样做的方法是使该方法返回一个 Observable,直到值为 .或者与 .已经尝试了几个小时,但没有成功。getUser()takeWhile!== nulltimer

任何帮助将不胜感激。

__

编辑:

这似乎有效,但使用我觉得很骇人听闻,我宁愿以另一种方式做:timer

在:user.service.ts

  setUserProfile() {
    timer(100).pipe(
      concatMap(() => {
        const username = this._authService.getUser();
        return this._adService.getUser(username)
      }),
      map(res => res[0])
    ).subscribe(profile => {
      this.userProfileSubject.next(profile);
    });
  }

app.component.ts ngOnInit

this._userService.setUserProfile();
    this._userService.userProfile$.pipe(
      map((user: Enduser) => this._userService.setUserPermissions(user)),
      takeUntil(this.ngUnsubscribe)
    ).subscribe();

编辑2:工作解决方案

isLoggedIn()是设置本地存储的方法。在这里,我正在等待它设置完毕,然后再继续获取用户配置文件信息。

this._authService.isLoggedIn().pipe(
      concatMap(() => {
        const username = this._authService.getUser();
        return this._adService.getUser(username)
      }),
      map(res => res[0])
    ).subscribe(profile => {
      this.userProfileSubject.next(profile);
    });
  }

isLoggedIn:

isLoggedIn(state): Observable<boolean> {

    ...

    return this.http.get(url, {withCredentials: true}).pipe(
      map((res: any) => {
        const token = res.mdoc.token;

        if (token) {
          localStorage.setItem(environment.JWT_TOKEN_NAME, token);
          return true;
        } else {
          return false;
        }
      })
  }
Angular 打字稿 RXJS

评论

0赞 Lansana Camara 8/3/2018
这没有意义......如果是同步的,那么它“需要完成才能使用”是什么意思?它总是在下一行执行之前完成,因为它是同步的。this._authService.getUser()
0赞 Lansana Camara 8/4/2018
伙计,老实说,你正在使你的代码比它需要的更复杂和不可读。你怎么知道 Okta 每次都会在 100 毫秒后完成?在现实世界中,您会遇到延迟之类的问题......这不是一个好的解决方案。保持简单...只是在您的 Okta 完成之前不要调用,而不是执行所有这些不必要的黑客计时器逻辑。阅读我最近对我的答案的评论。setUserProfile

答:

0赞 Ryan E. 8/3/2018 #1
const usernameObs = of(this._authService.getUser());
return usernameObs.pipe(
   flatMap(username => {
    return this._adService.getUser(username).pipe( 
       first(),
       map(result => result[0]),
       publishReplay(1),
       refCount()
    );
}))

可能有一种方法可以删除嵌套管道。我无法测试它,但这也应该有效,并且更干净一些:

const usernameObs = of(this._authService.getUser());
return usernameObs.pipe(
  flatMap(username => {
    return this._adService.getUser(username);
  }),
  first(),
  map(result => result[0]),
  publishReplay(1),
  refCount()
)

评论

0赞 kriskanya 8/4/2018
这看起来与我的一些尝试相似。正如我上面所说,问题是第一次返回 null。我可以以某种方式继续轮询它吗?尝试使用 ,但一直不起作用。this._authService.getUsertakeWhile
0赞 Ryan E. 8/4/2018
我相信有一个 retry() 运算符。learnrxjs.io/operators/error_handling/retry.html
0赞 Ryan E. 8/4/2018
没关系,这对你不起作用,因为这只会错误地重试
1赞 Lansana Camara 8/3/2018 #2

正如我的评论中所述,您想要等待完成的问题没有意义,因为如果是同步的(如您所说),那么它总是在执行下一行代码之前完成。this._authService.getUser()this._authService.getUser()

无论如何,在阅读了您的代码后,我想我知道您要做什么......

  1. 获取用户名表单this._authService.getUser()
  2. 将用户名传递给this._adService.getUser()
  3. 等待完成并将其值传递给可观察流,this._adService.getUser()userProfile$

为了实现这一点,你不需要任何那些花哨的 RxJS 运算符;您的代码可以像以下简单一样:

export class UserService {
  private userProfileSubject = new BehaviorSubject<Enduser>(new Enduser());
  userProfile$ = this.userProfileSubject.asObservable();

  constructor(
    private _adService: AdService,
    private _authService: AuthnService
  ) {}

  setUserProfile() {
    const username = this._authService.getUser();

    this._adService.getUser(username).subscribe((userProfile: Enduser) => {
      this.userProfileSubject.next(userProfile);
    });
  }
}

只需像我上面所做的那样发送到流,并在应用程序中的任何位置订阅它以获取用户配置文件数据。userProfile$

现在,在应用中的任何位置,只要用户配置文件数据向下发送,您都可以执行此操作来获取用户配置文件数据:

constructor(private _userService: UserService) {
  _userService.userProfile$.subscribe((userProfile: Enduser) => {
    console.log(userProfile);
  });
}

评论

0赞 kriskanya 8/4/2018
是的,你是对的:它一定不是同步的,否则我不会有这个问题。上面的问题是第一次回来(这是我所有尝试的问题)。你觉得怎么样?this._authService.getUser();null
0赞 Lansana Camara 8/4/2018
转到开发人员工具,然后手动查看本地存储。问自己几个问题:1.令牌是否真的存在于本地存储中?2. 如果是这样,它是否过期了?如果它未过期,则代码逻辑可能不正确。如果它已过期,则有您的问题。代码过期后返回 null。但这不应该是同步或异步的问题,因为 localStorage API 都是同步的。
0赞 kriskanya 8/4/2018
如果我在 之后 ,初始值为 null。我们正在使用 Okta 进行身份验证,并且正在设置令牌,但稍晚于加载应用程序之后。我放了一个——到那时令牌已经出现了。我已经更新了上面的最新消息。console.log(token)const token = localStorage.getItem(environment.JWT_TOKEN_NAME);getUser()tokentimeout(100)setUserProfile()
0赞 Lansana Camara 8/4/2018
好的,问题不在于本地存储,而在于数据直到几毫秒后才存储在那里。我会尝试弄清楚如何将逻辑的执行推迟到 Okta 设置令牌之后,但是我在示例中显示的逻辑应该可以正常工作。同样,请确保在 Okta 设置令牌之前不会调用。他们是否有某种可以挂钩的 API,例如设置令牌后触发的 en 事件?这是调用逻辑的理想场所。setUserProfilesetUserProfilesetUserProfile
0赞 kriskanya 8/4/2018
事实证明,我们有一个方法,它实际上设置了来自 Octa 的 localstorage 令牌。所以我用它代替了它。感谢您的指导,引导我朝着这个方向前进。timer
0赞 kriskanya 10/10/2018 #3

我的实现:

setUserProfile() {
    this.userProfile$ = this._authService.isLoggedIn(this.activatedRoute.snapshot).pipe(
      concatMap(() => {
        return this._adService.getUser(this._authService.getUser()).pipe(
          map(result => result[0]),
          publishReplay(1),
          refCount()
        );
      })
    )
    return this.userProfile$;
  }
}

_____

// _adService.getUser()

  getUser(username: string): Observable<Enduser> {
    const usernameUrl = encodeURIComponent(username);
    return this.http.get(`${environment.API_URL}person/${usernameUrl}`).pipe(
      map((res: any) => res.data)
    );
  }

_____

// _authService.getUser()

  public getUser(): string {
    const jwtHelper = new JwtHelperService()

    const token = localStorage.getItem(environment.JWT_TOKEN_NAME);
    if (!token || jwtHelper.isTokenExpired(token)) {
      return null;
    } else {
      const t = jwtHelper.decodeToken(token);
      return t.username;
    }
  }