Hi All (Especially the Aurelia core team hanging about round here)
I have an aurelia app using the "aurelia-http-client" to make requests to my back end API.
My back end API is a C# based service running on Nancy.
In my front end Iv'e abstracted the http client out to my own network lib as follows:
import { inject } from 'aurelia-framework';
import { Router } from 'aurelia-router';
import { HttpClient } from 'aurelia-http-client';
import environment from './environment';
@inject(HttpClient, Router)
export default class httpservice {
private http: HttpClient = null;
private router: Router = null;
private authService: any = null;
private authToken: string = "";
constructor(HttpClient, Router) {
this.http = HttpClient;
this.router = Router;
HttpClient.configure(http => {
http.withBaseUrl(environment.servicebase);
});
}
public setAuthService(authService: any) {
this.authService = authService;
}
public get(url: string, authObject?: any): any {
let myAuth = this.authService ? this.authService : authObject;
let myToken = "";
if (myAuth) {
myToken = myAuth.getAuthToken();
}
let self = this;
let client = this.http
.createRequest(url)
.asGet()
.withHeader("AuthenticationToken", myToken)
.withInterceptor({
responseError(responseError) {
console.log(responseError);
if (responseError.statusCode === 401) {
if (myAuth) {
myAuth.destroySession();
}
}
if (responseError.statusCode === 404) {
self.router.navigateToRoute("missing");
}
return responseError;
}
});
return client;
}
public post(url: string, postData: any, authObject?: any): any {
let myAuth = this.authService ? this.authService : authObject;
let myToken = "";
if (myAuth) {
myToken = myAuth.getAuthToken();
}
let self = this;
let client = this.http
.createRequest(url)
.asPost().withContent(postData)
.withHeader("AuthenticationToken", myToken)
.withInterceptor({
responseError(responseError) {
console.log(responseError);
if (responseError.statusCode === 401) {
if (myAuth) {
myAuth.destroySession();
}
}
if (responseError.statusCode === 404) {
self.router.navigateToRoute("missing");
}
return responseError;
}
});
return client;
}
}
and I then use this in my other modules/classes as follows:
import { Aurelia, inject } from 'aurelia-framework';
import HttpService from './httpservice';
import environment from './environment';
import { EventAggregator } from 'aurelia-event-aggregator';
@inject(EventAggregator, Aurelia, HttpService)
export default class Authservice {
public http: HttpService = null;
public app: Aurelia = null;
public ea: EventAggregator = null;
public authToken: any = null;
private loginUrl: string = "";
private logoutUrl: string = "";
private checkUrl: string = "";
constructor(eventAggregator, aurelia, httpService) {
this.http = httpService;
this.app = aurelia;
this.ea = eventAggregator;
this.loginUrl = "/login";
}
public getAuthToken() {
if (!sessionStorage[environment.tokenname] ||
(sessionStorage[environment.tokenname] == null)) {
return null;
}
return sessionStorage[environment.tokenname];
}
public login(loginName, password) {
let postData = {
loginName: loginName,
password: password
};
let client = this.http.post(this.loginUrl, postData);
client.send()
.then((response) => response.content)
.then((data) => {
if (data.error) {
this.ea.publish("loginMessage", { message: data.errorMessage });
return;
}
if (data.authenticationFailed) {
this.ea.publish("loginMessage", { message: "Invalid user name and/or password supplied." });
return;
}
if (data.accountSuspended) {
this.ea.publish("loginMessage", { message: "Your account has been suspended, please contact support." });
return;
}
sessionStorage[environment.tokenname] = data.token;
sessionStorage["displayedLoginName"] = data.displayName;
location.assign('#/');
this.app.setRoot('app');
})
.catch(() =>
{
debugger;
alert("Something bad happened trying to connect to server.");
});
}
public isAuthenticated() {
// TODO: hook this up to check auth token validity via rest call???
let token = this.getAuthToken();
return token !== null;
}
}
enum LoginStates {
LoginValid = 0,
BadUserNameOrPassword,
AccountSuspended
}
Please note I've stripped some of the code out of the auth library to reduce confusion
In general ALL of this works well. The interceptors get triggered when 401s and 404s occur, and if I add a 500 that get's handled too, so where all good there.
The problem I have is handling communication failures.
As you can see in the login routine, I have a catch following the then.
I expected that if the server couldn't be reached or some other base communications failure occurred, that this catch would trigger rather than the "then" and thus allow me to handle the error, but instead it does not.
What I get instead is this in the console:
Worse still, my login routine doesn't abort, it actually succeeds and allows the logged in page to be shown.
It seems that while the library is making the OPTIONS call (Which is when this error occurs) none of my user code is taken into account.
The OPTIONS call is required for successful pre-flight/ajax requests, so stopping this happening is not an option, and I feel that if the OPTIONS call did not abort, but made it to the POST call,t hat my error handling would then be taken into consideration.
It seems silly to be not able to trap errors like this, especially in today's mobile world where a device may be out of coverage or temporarily offline.
If anyone has any thoughts on how this can be solved, I'd love to hear them.
Update 1
My problem seems to be related to this one: aurelia-fetch-client - a promise was rejected with a non-error: [object Response]
However, I'm not using "useStandardConfiguration()" which is apparently the cause for that case. I'm also not using the fetch client, however I do note that the API in both clients is practically the same, so I wonder if the underlying code is also similar.