I need help creating a function that gets the categories from my API and also checks for status of the call. I have written my function like the code bellow but it keep showing me the bellow error:
Argument of type '(token: GetResult) => Subscription' is not assignable to parameter of type '(value: GetResult, index: number) => ObservableInput<any>'
Here is the code of my function:
getCategories() {
return from(Preferences.get({ key: "TOKEN_KEY" })).pipe(
switchMap((token) => {
const headers = new HttpHeaders().set(
"Authorization",
`Bearer ${token.value}`
);
return this.httpClient
.get(`${environment.apiUrl}categories`, {
headers,
observe: "response",
})
.subscribe(
(res) => {
return res.body;
},
(error) => {
console.log(error.status);
if (error.status === 400) {
console.log(error.error.message);
}
if (error.status === 401) {
this.authService.logout();
this.router.navigateByUrl("/login", { replaceUrl: true });
}
}
);
})
);
}
You should not use .subscribe inside a subscription. .subscribe returns a Subscription which can't be assigned to the Observable that a switchMap should return.
Use catchError to to handle the error case and a map to handle the success case.
getCategories() {
return from(Preferences.get({ key: 'TOKEN_KEY' })).pipe(
switchMap(token => {
const headers = new HttpHeaders().set('Authorization', `Bearer ${token.value}`);
return this.httpClient.get(
`${environment.apiUrl}categories`,
{ headers, observe: 'response' }
)
}),
catchError(error => {
console.log(error.status);
if (error.status === 400) {
console.log(error.error.message);
}
if (error.status === 401) {
this.authService.logout();
this.router.navigateByUrl('/login', { replaceUrl: true });
}
return EMPTY
}),
map(res => res.body)
)
};
Related
The below angular code switchMap is not working, I'm not sure what error I made. Under the switchMap second API call not triggering '/upload/file'
zip(
this.service.submitForm(formValue),
this.service.upload(fData)
).subscribe(
([submitForm, upload]) => {
if (submitForm === 'success' && upload === 'Ok') {
//Redirect confirmation page
}
},
(err) => {
console.log(err, 'ERORORO');
}
)
//Service code
upload(formData): Observable <any> {
return this.sessionService.keepAlive().pipe(
switchMap(data => {
let token = data.jwtToken;
console.log(token, 'TOKEN SESSION');
// getting output as Bearer xyz
// with formData as req
const request_config = {
headers: {
"Authorization": token
}
};
console.log("REQUEST CONFIG", request_config); // getting output
return this.http.post < any > (
'/upload/file',
formData,
request_config
).pipe( // this is not working
map((res) => {
console.log(res, 'RESPONSE');
return res.status;
}),
catchError((error: HttpErrorResponse) => {
throw error;
})
)
})
)
}
I am trying to call the MSAL silentTokenrefresh method from Angular authInterceptor whenever the 401 hits. And then i am trying to recall the failed request again with a new token so the service won't be interrupted. I have followed this stackoverflow link (answered by Andrei Ostrovski) and implemented the same in my application.
There are two problem with refreshToken() method.
the catchError expects return variable where i put EMPTY. The code is happy, but it is NOT triggering back the failed request where the total purpose is not achieved. It means that whenever it encounters 401 and it is allowing to acquire the new token and then it is not triggering the failed requests.
To resolve the above one, i just took another approach (instead of pipe, i used subscribed but there the return is not applicable) and then it became completely invalid.
Could you please suggest me here to make it work?
My complete authInterceptor file:
addAuthHeader(request) {
const tokenType = "Bearer";
const authHeader = this.sessionService.getAccessToken();
if (authHeader) {
return request.clone({
setHeaders: {
"Authorization": tokenType + " "+ authHeader
}
});
}
return request;
}
refreshToken(): Observable<any> {
if (this.refreshTokenInProgress) {
return new Observable(observer => {
this.tokenRefreshed$.subscribe(() => {
observer.next();
observer.complete();
});
});
} else {
this.refreshTokenInProgress = true;
// Getting the scope.
const loginRequest: { scopes: any } = {
scopes: ['openId','profile'],
};
return this.msalAuthService.acquireTokenSilent(loginRequest).pipe(
tap((payloadInfo)=>{
this.authServices.setSessions(payloadInfo);
this.refreshTokenInProgress = false;
this.tokenRefreshedSource.next();
}),
catchError(() => {
this.refreshTokenInProgress = false;
this.authServices.logout();
return EMPTY;
})
)
}
}
handleResponseError(error, request?, next?) {
// Invalid token error
if (error.status === 401) {
return this.refreshToken().pipe(
switchMap(() => {
request = this.addAuthHeader(request);
return next.handle(request);
}),
catchError(e => {
if (e.status !== 401) {
return this.handleResponseError(e);
} else {
this.authentiCationService.logout();
this.router.navigate(["logout"], {
replaceUrl: true
});
}
}));
}
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.totalRequests++;
req = req.clone({ headers: req.headers.set("Accept", "*/*") });
req = req.clone({
headers: req.headers.set("Content-Type", "application/json")
});
req = this.addAuthHeader(req);
const started = Date.now();
return next.handle(req).pipe(
tap(
(event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// some internal logic for the success scenario
}
},
(err: any) => {
this.handleResponseError(err, req, next);
}
)
);
}
The compile time error you are getting is because you are returning the Subscription instead of the Observable. Please take a look at the difference between them: https://angular.io/guide/observables
You have to return the Observable from your code. In catchError return Observable.throw(error.statusText);
The thing is if I have a 401 error (token expired) I am making a request for the server to refresh the token. It handles all the requests been sent. It refreshes the token but interrupts the previous request. How to continue request after cathing error?
private handleAuthError(err: HttpErrorResponse): Observable<any> {
if (err.status === 401 || err.status === 403) {
this.authService.refreshToken(this.userData.accessToken, this.userData.refreshToken, this.userData.tokenType).subscribe((resp: any) => {
this.userData.accessToken = resp.access_token;
this.authService.currentUserSubject.next(this.userData);
const userData: User = new User(resp);
localStorage.setItem('currentUser', JSON.stringify(this.authService.currentUserSubject));
localStorage.setItem(`${JWT_TOKEN_KEY}`, JSON.stringify(userData.accessToken));
})
return of(err.message);
}
return throwError(err);
}
public intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const token: string = JSON.parse(localStorage.getItem(`${JWT_TOKEN_KEY}`));
const isLoggedIn = token;
const isApiUrl = request.url.startsWith(environment.apiUrl);
if (isLoggedIn && isApiUrl) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
}
console.log(request);
return next.handle(request)
.pipe(
catchError((x: any) => this.handleAuthError(x)));
}
}
Just make a new request, you can't use the same one since it already ended
You can try this function to catch any request while error occurs and rewind that request again.
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
map((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
return event;
}
}),
catchError(error => {
if (error instanceof HttpErrorResponse) {
if (error.status === 401) {
if (request.url !== Constants.AUTH_ENDPOINT) {
this.auth.collectFailedRequest(request);
return this.auth.refreshAuthToken().pipe(
switchMap((data: any) => {
if (data && data.access_token && data.refresh_token) {
localStorage.setItem(Constants.ACCESS_TOKEN, JSON.stringify(data.access_token));
localStorage.setItem(Constants.REFRESH_TOKEN, JSON.stringify(data.refresh_token));
this.auth.cachedRequests = this.auth.cachedRequests.clone({
setHeaders: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${data.access_token}`
}
});
return next.handle(this.auth.cachedRequests); // rewinds previously requested http call
} else {
toastr.error(error.error.toUpperCase(), error.status);
this.auth.removeAllTokens();
setTimeout(() => {
window.location.reload();
}, 1000);
}
})
);
} else {
if (request.body.grant_type === 'refresh') {
this.auth.removeAllTokens();
window.location.reload();
} else {
console.log('no refresh grant type');
}
}
} else {
return throwError(error);
}
}
})
);
}
I have a class method that uses BehaviorSubject and fromFetch from rxjs and returns an observable. I'm trying to subscribe to method outside of class.
I can console.log the method I get AnonymousSubject {_isScalar: false, observers: Array(0), closed: false, isStopped: false, hasError: false, …}
export class Client {
constructor(opts) {
...
}
request(operation) {
const option$ = new BehaviorSubject(null)
const body = JSON.stringify({
query: operation.query,
variables: operation.variables
})
option$.next(body)
return option$.pipe(
switchMap(body => {
return fromFetch(url, {
method: 'POST',
body,
headers: {
'Content-Type': 'application/json',
...fetchOpts.headers
},
...fetchOpts
}).pipe(
switchMap(response => {
if (response.ok) {
// OK return data
return response.json()
} else {
// Server is returning a status requiring the client to try something else.
return of({
error: true,
message: `Error ${response.status}`
})
}
}),
catchError(err => {
// Network or other error, handle appropriately
console.error(err)
return of({ error: true, message: err.message })
})
)
})
)
}
}
I'm want to call method and subscribe to it like so
let client = new Client({...})
function handleRequest(operations) {
let data = client.request(operations)
data.subscribe(...)
}
When I add .subscribe to data it throws error: Uncaught TypeError: You provided an invalid object where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.
Problem is that you are returning simple response.json() You should return an observable like of(response.json()) from switchMap's if (response.ok) block - See the code below -
export class Client {
constructor(opts) {
...
}
request(operation) {
const option$ = new BehaviorSubject(null)
const body = JSON.stringify({
query: operation.query,
variables: operation.variables
})
option$.next(body)
return option$.pipe(
switchMap(body => {
return fromFetch(url, {
method: 'POST',
body,
headers: {
'Content-Type': 'application/json',
...fetchOpts.headers
},
...fetchOpts
}).pipe(
switchMap(response => {
if (response.ok) {
// OK return data
return of(response.json())
} else {
// Server is returning a status requiring the client to try something else.
return of({
error: true,
message: `Error ${response.status}`
})
}
}),
catchError(err => {
// Network or other error, handle appropriately
console.error(err)
return of({ error: true, message: err.message })
})
)
})
)
}
}
I have some code that goes to my server and returns some data. I have noticed, that i am unable to catch/handle a 403 response.
Code:
canActivate(route: ActivatedRouteRequest, state: RouterStateSnapshot): Observable <boolean> {
return this.myService.getEntitlements()
.pipe(
map(data => {
this.isEntitled = data.hasEntitlements;
if(this.isEntitled === true) {
return true;
}
else {
return false;
}
}),
catchError((err: any) => {
return of(false);
})
};
}