Auth-0 aplikacja do odpalania rakiet – frontend

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<string[]> {
		return Promise.resolve(['BB', 'Tsar bomba']);
	}
	public putMissilesToLocation(latitude: number, longitude: number, spec: MissileSpec) : Promise<void> {
		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

<p>${error}</p>
<div>
<form submit.delegate="sendMissiles()">
	<p>
		<label>Długość geograficzna</label>
		<input type="number" value.bind="longitude">
	</p>
	<p>
		<label>Szerokość geograficzna</label>
		<input type="number" value.bind="latitude">
	</p>
	<p>
		<label>Ilość pocisków</label>
		<input type="number" value.bind="missileCount">
	</p>
	<p>
		<label>Typ pocisków</label>
		<select value.bind="selectedMissileType">
			<option repeat.for="missileType of missileTypes" model.bind="missileType">${missileType}</option>
		</select>
	</p>
	<p>
		<button type="submit">Wyślij pociski</button>
	</p>
</form>
</div>

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<string[]> {
	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<string> {
	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<void> {
	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”

  1. Pingback: dotnetomaniak.pl

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *