258 lines
9.4 KiB
TypeScript
258 lines
9.4 KiB
TypeScript
import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
|
|
import { Injectable } from '@angular/core';
|
|
|
|
import { TranslateService } from '@ngx-translate/core';
|
|
|
|
import { OpenSlidesStatusService } from './openslides-status.service';
|
|
import { formatQueryParams, QueryParams } from '../definitions/query-params';
|
|
|
|
/**
|
|
* Enum for different HTTPMethods
|
|
*/
|
|
export enum HTTPMethod {
|
|
GET = 'get',
|
|
POST = 'post',
|
|
PUT = 'put',
|
|
PATCH = 'patch',
|
|
DELETE = 'delete'
|
|
}
|
|
|
|
export interface DetailResponse {
|
|
detail: string | string[];
|
|
args?: string[];
|
|
}
|
|
|
|
function isDetailResponse(obj: any): obj is DetailResponse {
|
|
return (
|
|
obj &&
|
|
typeof obj === 'object' &&
|
|
(typeof obj.detail === 'string' || obj.detail instanceof Array) &&
|
|
(!obj.args || obj.args instanceof Array)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Service for managing HTTP requests. Allows to send data for every method. Also (TODO) will do generic error handling.
|
|
*/
|
|
@Injectable({
|
|
providedIn: 'root'
|
|
})
|
|
export class HttpService {
|
|
/**
|
|
* http headers used by most requests
|
|
*/
|
|
private defaultHeaders: HttpHeaders;
|
|
|
|
/**
|
|
* Construct a HttpService
|
|
*
|
|
* Sets the default headers to application/json
|
|
*
|
|
* @param http The HTTP Client
|
|
* @param translate
|
|
* @param timeTravel requests are only allowed if history mode is disabled
|
|
*/
|
|
public constructor(
|
|
private http: HttpClient,
|
|
private translate: TranslateService,
|
|
private OSStatus: OpenSlidesStatusService
|
|
) {
|
|
this.defaultHeaders = new HttpHeaders().set('Content-Type', 'application/json');
|
|
}
|
|
|
|
/**
|
|
* Send the a http request the the given path.
|
|
* Optionally accepts a request body.
|
|
*
|
|
* @param path the target path, usually starting with /rest
|
|
* @param method the required HTTP method (i.e get, post, put)
|
|
* @param data optional, if sending a data body is required
|
|
* @param queryParams optional queryparams to append to the path
|
|
* @param customHeader optional custom HTTP header of required
|
|
* @param responseType optional response type, default set to json (i.e 'arraybuffer')
|
|
* @returns a promise containing a generic
|
|
*/
|
|
private async send<T>(
|
|
path: string,
|
|
method: HTTPMethod,
|
|
data?: any,
|
|
queryParams?: QueryParams,
|
|
customHeader?: HttpHeaders,
|
|
responseType?: string
|
|
): Promise<T> {
|
|
// end early, if we are in history mode
|
|
if (this.OSStatus.isInHistoryMode && method !== HTTPMethod.GET) {
|
|
throw this.handleError('You cannot make changes while in history mode');
|
|
}
|
|
|
|
// there is a current bug with the responseType.
|
|
// https://github.com/angular/angular/issues/18586
|
|
// castting it to 'json' allows the usage of the current array
|
|
if (!responseType) {
|
|
responseType = 'json';
|
|
}
|
|
|
|
let url = path + formatQueryParams(queryParams);
|
|
if (url[0] !== '/') {
|
|
console.warn(`Please prefix the URL "${url}" with a slash.`);
|
|
url = '/' + url;
|
|
}
|
|
if (this.OSStatus.isPrioritizedClient) {
|
|
url = '/prioritize' + url;
|
|
}
|
|
|
|
const options = {
|
|
body: data,
|
|
headers: customHeader ? customHeader : this.defaultHeaders,
|
|
responseType: responseType as 'json'
|
|
};
|
|
|
|
try {
|
|
return await this.http.request<T>(method, url, options).toPromise();
|
|
} catch (e) {
|
|
throw this.handleError(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Takes an error thrown by the HttpClient. Processes it to return a string that can
|
|
* be presented to the user.
|
|
* @param e The error thrown.
|
|
* @returns The prepared and translated message for the user
|
|
*/
|
|
private handleError(e: any): string {
|
|
let error = this.translate.instant('Error') + ': ';
|
|
// If the error is a string already, return it.
|
|
if (typeof e === 'string') {
|
|
return error + e;
|
|
}
|
|
|
|
// If the error is no HttpErrorResponse, it's not clear what is wrong.
|
|
if (!(e instanceof HttpErrorResponse)) {
|
|
console.error('Unknown error thrown by the http client: ', e);
|
|
error += this.translate.instant('An unknown error occurred.');
|
|
return error;
|
|
}
|
|
|
|
if (e.status === 405) {
|
|
// this should only happen, if the url is wrong -> a bug.
|
|
error += this.translate.instant(
|
|
'The requested method is not allowed. Please contact your system administrator.'
|
|
);
|
|
} else if (!e.error) {
|
|
error += this.translate.instant("The server didn't respond.");
|
|
} else if (typeof e.error === 'object') {
|
|
if (isDetailResponse(e.error)) {
|
|
error += this.processDetailResponse(e.error);
|
|
} else {
|
|
error = Object.keys(e.error)
|
|
.map(key => {
|
|
const capitalizedKey = key.charAt(0).toUpperCase() + key.slice(1);
|
|
return this.translate.instant(capitalizedKey) + ': ' + this.processDetailResponse(e.error[key]);
|
|
})
|
|
.join(', ');
|
|
}
|
|
} else if (e.status === 500) {
|
|
error += this.translate.instant('A server error occured. Please contact your system administrator.');
|
|
} else if (e.status > 500) {
|
|
error += this.translate.instant('The server could not be reached.') + ` (${e.status})`;
|
|
} else {
|
|
error += e.message;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
* Errors from the servers may be string or array of strings. This function joins the strings together,
|
|
* if an array is send.
|
|
* @param str a string or a string array to join together.
|
|
* @returns Error text(s) as single string
|
|
*/
|
|
private processDetailResponse(response: DetailResponse): string {
|
|
let message: string;
|
|
if (response instanceof Array) {
|
|
message = response.join(' ');
|
|
} else if (response.detail instanceof Array) {
|
|
message = response.detail.join(' ');
|
|
} else {
|
|
message = response.detail;
|
|
}
|
|
message = this.translate.instant(message);
|
|
|
|
if (response.args && response.args.length > 0) {
|
|
for (let i = 0; i < response.args.length; i++) {
|
|
message = message.replace(`{${i}}`, response.args[i].toString());
|
|
}
|
|
}
|
|
return message;
|
|
}
|
|
|
|
/**
|
|
* Executes a get on a path with a certain object
|
|
* @param path The path to send the request to.
|
|
* @param data An optional payload for the request.
|
|
* @param queryParams Optional params appended to the path as the query part of the url.
|
|
* @param header optional HTTP header if required
|
|
* @param responseType option expected response type by the request (i.e 'arraybuffer')
|
|
* @returns A promise holding a generic
|
|
*/
|
|
public async get<T>(
|
|
path: string,
|
|
data?: any,
|
|
queryParams?: QueryParams,
|
|
header?: HttpHeaders,
|
|
responseType?: string
|
|
): Promise<T> {
|
|
return await this.send<T>(path, HTTPMethod.GET, data, queryParams, header, responseType);
|
|
}
|
|
|
|
/**
|
|
* Executes a post on a path with a certain object
|
|
* @param path The path to send the request to.
|
|
* @param data An optional payload for the request.
|
|
* @param queryParams Optional params appended to the path as the query part of the url.
|
|
* @param header optional HTTP header if required
|
|
* @returns A promise holding a generic
|
|
*/
|
|
public async post<T>(path: string, data?: any, queryParams?: QueryParams, header?: HttpHeaders): Promise<T> {
|
|
return await this.send<T>(path, HTTPMethod.POST, data, queryParams, header);
|
|
}
|
|
|
|
/**
|
|
* Executes a put on a path with a certain object
|
|
* @param path The path to send the request to.
|
|
* @param data An optional payload for the request.
|
|
* @param queryParams Optional params appended to the path as the query part of the url.
|
|
* @param header optional HTTP header if required
|
|
* @returns A promise holding a generic
|
|
*/
|
|
public async patch<T>(path: string, data?: any, queryParams?: QueryParams, header?: HttpHeaders): Promise<T> {
|
|
return await this.send<T>(path, HTTPMethod.PATCH, data, queryParams, header);
|
|
}
|
|
|
|
/**
|
|
* Executes a put on a path with a certain object
|
|
* @param path The path to send the request to.
|
|
* @param data An optional payload for the request.
|
|
* @param queryParams Optional params appended to the path as the query part of the url.
|
|
* @param header optional HTTP header if required
|
|
* @returns A promise holding a generic
|
|
*/
|
|
public async put<T>(path: string, data?: any, queryParams?: QueryParams, header?: HttpHeaders): Promise<T> {
|
|
return await this.send<T>(path, HTTPMethod.PUT, data, queryParams, header);
|
|
}
|
|
|
|
/**
|
|
* Makes a delete request.
|
|
* @param url The path to send the request to.
|
|
* @param data An optional payload for the request.
|
|
* @param queryParams Optional params appended to the path as the query part of the url.
|
|
* @param header optional HTTP header if required
|
|
* @returns A promise holding a generic
|
|
*/
|
|
public async delete<T>(path: string, data?: any, queryParams?: QueryParams, header?: HttpHeaders): Promise<T> {
|
|
return await this.send<T>(path, HTTPMethod.DELETE, data, queryParams, header);
|
|
}
|
|
}
|