Odpalenie pocisków
Implementację formularza zacząłem od utworzenia klasy pośrednika (ang. proxy), w której będzie koncentrować komunikacja z serwisem do odpalania rakiet. Klasa posiada metody odpowiadające punktom końcowym serwisu. Zawiera tylko tymczasową implementację, wystarczającą do implementacji formularza.
export class RocketLauncherServiceProxy { public getMissileTypes(): Promise{ return Promise.resolve(['BB', 'Tsar bomba']); } public putMissilesToLocation(latitude: number, longitude: number, spec: MissileSpec) : Promise { return Promise.resolve(); } } export interface MissileSpec { type: string; amount: number; }
Powyższą klasę wstrzyknąłem do klasy LaunchConsole.
... import {RocketLauncherServiceProxy} from './rocket-launcher-service-proxy'; @inject(AuthService, RocketLauncherServiceProxy) export class LaunchConsole { private authService: AuthService; private rocketLauncherService: RocketLauncherServiceProxy; constructor( authService: AuthService, rocketLauncherService: RocketLauncherServiceProxy) { this.authService = authService; this.rocketLauncherService = rocketLauncherService; } ... }
Formularz musi zawierać pola do wprowadzenia długości i szerokości geograficznej, ilości oraz typu wysłanych pocisków. Na wszystkie te wartości dodałem pola w klasie. Dodałem też pole na listę wszystkich dostępnych typów pocisków oraz na ewentualny komunikat o błędzie.
public latitude: number; public longitude: number; public selectedMissileType: string | undefined; public missileAmount: number; public missileTypes: string[]; public error: string;
Typescript wymaga przypisania wartości do wszystkich pól nie akceptujących undefined jako wartości. Dodatkowo dodałem metodę activate w celu uzupełnienia pola missileTypes danymi z serwera. W przypadku gdy pobranie danych się nie uda, zostanie wyświetlony stosowny komunikat.
constructor( authService: AuthService, rocketLauncherService: RocketLauncherServiceProxy) { this.authService = authService; this.rocketLauncherService = rocketLauncherService; // By default aim to Gulf of Guinea - pretty safe this.latitude = 0.0; this.longitude = 0.0; this.missileAmount = 0; this.missileTypes = []; this.error = ''; } public activate() : void { this .rocketLauncherService .getMissileTypes() .then( missileTypes => this.missileTypes = missileTypes error => this.error = error); }
Ostatnim elementem view-modelu jest metoda launchMissiles, która przekazuje wartości pól do metody putMissilesToLocation.
public sendMissiles(): void { this .rocketLauncherService .putMissilesToLocation( this.latitude, this.longitude, { amount: this.missileAmount, type: this.selectedMissileType }) .catch(error => this.error = error); }
Następnie dodałem formularz do widoku
${error}
submit.delegate – służy do wskazania metody odpalonej po kliknięciu w przycisk „Wyślij pociski”, value.bind – wskazuje pole do Aurelia będzie przekazywać wartość pola, repeat.for =”missileType of missileTypes” sprawia, że element option będzie powielony dla każdego elementu w kolekcji missileTypes.
Do komunikacji z backendem aplikacja potrzebuje mechanizmu do wysyłania zapytań http, Aurelia, sama z siebie, nie dostarcza takiego mechanizmu, jest to celowe działanie twórców biblioteki, starają się oni aby poszczególne komponenty-wtyczki mogły być wymienialne. Sami twórcy Aurelii stworzyli dwie wtyczki do obsługi protokołu http. Ja zdecydowałem się użyć „aurelia-fetch-client”
npm install --save aurelia-fetch-client
Biblioteka ta zawiera klasę HttpClient. Pobranie typów pocisków przy jej pomocy wygląda następująco
public getMissileTypes(): Promise{ let httpClient = new HttpClient(); return httpClient .get('https://localhost:5001/missile-types') .then(response => response.json()); }
Kod jest prosty, do metody get przekazuje adres z którego ma być pobrana lista pocisków, a po otrzymaniu odpowiedzi traktuje treść jako obiekt json. Ten kod niestety nie działa. Po pierwsze serwer zwraca kod błędu 401 – Unauthorized. Nie ma w tym nic dziwnego, kod nie zawiera przekazania danych autoryzacyjnych. Biblioteka Auth0 posiada metodę checkSession pozwalającą pobrać token autoryzacyjny. W klasie AuthService otoczyłem ją metodą getAccessToken
public getAccessToken() : Promise{ return new Promise((resolve, reject) => { this.webAuth.checkSession({}, (error, result) => { if (error) { reject(error); return; } resolve(result.accessToken); }) }); }
Po uruchomieniu tego kodu lista typów pocisków dalej jest nieuzupełniona, zostaje wyświetlony komunikat o błędzie. Więcej szczegółów znajduje się w narzędziach deweloperskich, które w większości przeglądarek są wyświetlone po wciśnięciu F12.
Access to fetch at 'https://localhost:5001/missile-types/' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled
Jest to spowodowane faktem iż przeglądarki zawierają mechanizm CORS zabezpieczający przed niechcianą komunikacją skryptów javascript z serwerem. Aby wywołanie działało backend musi potwierdzić że skrypty z danej strony mogą się z nim kontaktować.
Aby backend udzielił właściwej odpowiedzi należy dodać następujące fragmenty kodu:
public void ConfigureServices(IServiceCollection services) { //... services.AddCors(options => { options.AddDefaultPolicy(configurePolicy => { configurePolicy.WithOrigins("http://localhost:8080").AllowAnyHeader().AllowAnyMethod(); }); }); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { //... app.UseCors(); //... }
Ostatnim krokiem jest zmiana metody putMissilesToLocation w klasie RocketLauncherServiceProxy tak aby komunikowała się z serwisem. Metoda ta niewiele się różni od metody getMissileTypes
public putMissilesToLocation(latitude: number, longitude: number, spec: MissileSpec) : Promise{ return this .authService .getAccessToken() .then(accessToken => { let httpClient = new HttpClient(); httpClient.configure({ headers: { 'Authorization': `Bearer ${accessToken}` } }) return httpClient .put(`https://localhost:5001/locations/${latitude}/${longitude}`, spec) .then(response => {}); }); }
Jedna odpowiedź do “Auth-0 aplikacja do odpalania rakiet – frontend”