adjust frontend to new backend
This commit is contained in:
339
templates/http/http.mustache
Normal file
339
templates/http/http.mustache
Normal file
@@ -0,0 +1,339 @@
|
||||
{{#platforms}}
|
||||
{{#node}}
|
||||
// TODO: evaluate if we can easily get rid of this library
|
||||
import {{^supportsES6}}* as{{/supportsES6}} FormData from "form-data";
|
||||
import { URLSearchParams } from 'url';
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
{{/node}}
|
||||
{{/platforms}}
|
||||
{{#platforms}}
|
||||
{{^deno}}
|
||||
import {{^supportsES6}}* as{{/supportsES6}} URLParse from "url-parse";
|
||||
{{/deno}}
|
||||
{{/platforms}}
|
||||
import { Observable, from } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'../rxjsStub{{extensionForDeno}}'{{/useRxJS}};
|
||||
|
||||
{{#platforms}}
|
||||
{{^deno}}
|
||||
{{#frameworks}}
|
||||
{{#fetch-api}}
|
||||
export * from './isomorphic-fetch';
|
||||
{{/fetch-api}}
|
||||
{{#jquery}}
|
||||
export * from './jquery';
|
||||
{{/jquery}}
|
||||
{{/frameworks}}
|
||||
{{/deno}}
|
||||
{{/platforms}}
|
||||
|
||||
/**
|
||||
* Represents an HTTP method.
|
||||
*/
|
||||
export enum HttpMethod {
|
||||
GET = "GET",
|
||||
HEAD = "HEAD",
|
||||
POST = "POST",
|
||||
PUT = "PUT",
|
||||
DELETE = "DELETE",
|
||||
CONNECT = "CONNECT",
|
||||
OPTIONS = "OPTIONS",
|
||||
TRACE = "TRACE",
|
||||
PATCH = "PATCH"
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an HTTP file which will be transferred from or to a server.
|
||||
*/
|
||||
{{#platforms}}
|
||||
{{#node}}
|
||||
export type HttpFile = {
|
||||
data: {{{fileContentDataType}}},
|
||||
name: string
|
||||
};
|
||||
{{/node}}
|
||||
{{^node}}
|
||||
export type HttpFile = {{{fileContentDataType}}} & { readonly name: string };
|
||||
{{/node}}
|
||||
{{/platforms}}
|
||||
|
||||
{{#platforms}}
|
||||
{{#deno}}
|
||||
/**
|
||||
* URLParse Wrapper for Deno
|
||||
*/
|
||||
class URLParse {
|
||||
private url: URL;
|
||||
|
||||
constructor(address: string, _parser: boolean) {
|
||||
this.url = new URL(address);
|
||||
}
|
||||
|
||||
public set(_part: 'query', obj: {[key: string]: string | undefined}) {
|
||||
for (const key in obj) {
|
||||
const value = obj[key];
|
||||
if (value) {
|
||||
this.url.searchParams.set(key, value);
|
||||
} else {
|
||||
this.url.searchParams.set(key, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get query() {
|
||||
const obj: {[key: string]: string} = {};
|
||||
for (const [key, value] of this.url.searchParams.entries()) {
|
||||
obj[key] = value;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return this.url.toString();
|
||||
}
|
||||
}
|
||||
{{/deno}}
|
||||
{{/platforms}}
|
||||
|
||||
export class HttpException extends Error {
|
||||
public constructor(msg: string) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the body of an outgoing HTTP request.
|
||||
*/
|
||||
export type RequestBody = undefined | string | FormData | URLSearchParams;
|
||||
|
||||
/**
|
||||
* Represents an HTTP request context
|
||||
*/
|
||||
export class RequestContext {
|
||||
private headers: { [key: string]: string } = {};
|
||||
private body: RequestBody = undefined;
|
||||
private url: URLParse;
|
||||
{{#platforms}}
|
||||
{{#node}}
|
||||
private agent: http.Agent | https.Agent | undefined = undefined;
|
||||
{{/node}}
|
||||
{{/platforms}}
|
||||
|
||||
/**
|
||||
* Creates the request context using a http method and request resource url
|
||||
*
|
||||
* @param url url of the requested resource
|
||||
* @param httpMethod http method
|
||||
*/
|
||||
public constructor(url: string, private httpMethod: HttpMethod) {
|
||||
this.url = new URLParse(url, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the url set in the constructor including the query string
|
||||
*
|
||||
*/
|
||||
public getUrl(): string {
|
||||
return this.url.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the url set in the constructor with this url.
|
||||
*
|
||||
*/
|
||||
public setUrl(url: string) {
|
||||
this.url = new URLParse(url, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the body of the http request either as a string or FormData
|
||||
*
|
||||
* Note that setting a body on a HTTP GET, HEAD, DELETE, CONNECT or TRACE
|
||||
* request is discouraged.
|
||||
* https://httpwg.org/http-core/draft-ietf-httpbis-semantics-latest.html#rfc.section.7.3.1
|
||||
*
|
||||
* @param body the body of the request
|
||||
*/
|
||||
public setBody(body: RequestBody) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public getHttpMethod(): HttpMethod {
|
||||
return this.httpMethod;
|
||||
}
|
||||
|
||||
public getHeaders(): { [key: string]: string } {
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
public getBody(): RequestBody {
|
||||
return this.body;
|
||||
}
|
||||
|
||||
public setQueryParam(name: string, value: string) {
|
||||
let queryObj = this.url.query;
|
||||
queryObj[name] = value;
|
||||
this.url.set("query", queryObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a cookie with the name and value. NO check for duplicate cookies is performed
|
||||
*
|
||||
*/
|
||||
public addCookie(name: string, value: string): void {
|
||||
if (!this.headers["Cookie"]) {
|
||||
this.headers["Cookie"] = "";
|
||||
}
|
||||
this.headers["Cookie"] += name + "=" + value + "; ";
|
||||
}
|
||||
|
||||
public setHeaderParam(key: string, value: string): void {
|
||||
this.headers[key] = value;
|
||||
}
|
||||
{{#platforms}}
|
||||
{{#node}}
|
||||
|
||||
public setAgent(agent: http.Agent | https.Agent) {
|
||||
this.agent = agent;
|
||||
}
|
||||
|
||||
public getAgent(): http.Agent | https.Agent | undefined {
|
||||
return this.agent;
|
||||
}
|
||||
{{/node}}
|
||||
{{/platforms}}
|
||||
}
|
||||
|
||||
export interface ResponseBody {
|
||||
text(): Promise<string>;
|
||||
binary(): Promise<{{{fileContentDataType}}}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to generate a `ResponseBody` from binary data
|
||||
*/
|
||||
export class SelfDecodingBody implements ResponseBody {
|
||||
constructor(private dataSource: Promise<{{{fileContentDataType}}}>) {}
|
||||
|
||||
binary(): Promise<{{{fileContentDataType}}}> {
|
||||
return this.dataSource;
|
||||
}
|
||||
|
||||
async text(): Promise<string> {
|
||||
const data: {{{fileContentDataType}}} = await this.dataSource;
|
||||
{{#platforms}}
|
||||
{{#node}}
|
||||
return data.toString();
|
||||
{{/node}}
|
||||
{{#browser}}
|
||||
// @ts-ignore
|
||||
if (data.text) {
|
||||
// @ts-ignore
|
||||
return data.text();
|
||||
}
|
||||
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener("load", () => resolve(reader.result as string));
|
||||
reader.addEventListener("error", () => reject(reader.error));
|
||||
reader.readAsText(data);
|
||||
});
|
||||
{{/browser}}
|
||||
{{#deno}}
|
||||
return data.text();
|
||||
{{/deno}}
|
||||
{{/platforms}}
|
||||
}
|
||||
}
|
||||
|
||||
export class ResponseContext {
|
||||
public constructor(
|
||||
public httpStatusCode: number,
|
||||
public headers: { [key: string]: string },
|
||||
public body: ResponseBody
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Parse header value in the form `value; param1="value1"`
|
||||
*
|
||||
* E.g. for Content-Type or Content-Disposition
|
||||
* Parameter names are converted to lower case
|
||||
* The first parameter is returned with the key `""`
|
||||
*/
|
||||
public getParsedHeader(headerName: string): { [parameter: string]: string } {
|
||||
const result: { [parameter: string]: string } = {};
|
||||
if (!this.headers[headerName]) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const parameters = this.headers[headerName].split(";");
|
||||
for (const parameter of parameters) {
|
||||
let [key, value] = parameter.split("=", 2);
|
||||
key = key.toLowerCase().trim();
|
||||
if (value === undefined) {
|
||||
result[""] = key;
|
||||
} else {
|
||||
value = value.trim();
|
||||
if (value.startsWith('"') && value.endsWith('"')) {
|
||||
value = value.substring(1, value.length - 1);
|
||||
}
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async getBodyAsFile(): Promise<HttpFile> {
|
||||
const data = await this.body.binary();
|
||||
const fileName = this.getParsedHeader("content-disposition")["filename"] || "";
|
||||
{{#platforms}}
|
||||
{{#node}}
|
||||
return { data, name: fileName };
|
||||
{{/node}}
|
||||
{{^node}}
|
||||
const contentType = this.headers["content-type"] || "";
|
||||
try {
|
||||
return new File([data], fileName, { type: contentType });
|
||||
} catch (error) {
|
||||
/** Fallback for when the File constructor is not available */
|
||||
return Object.assign(data, {
|
||||
name: fileName,
|
||||
type: contentType
|
||||
});
|
||||
}
|
||||
{{/node}}
|
||||
{{/platforms}}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a heuristic to get a body of unknown data structure.
|
||||
* Return as string if possible, otherwise as binary.
|
||||
*/
|
||||
public getBodyAsAny(): Promise<string | {{{fileContentDataType}}} | undefined> {
|
||||
try {
|
||||
return this.body.text();
|
||||
} catch {}
|
||||
|
||||
try {
|
||||
return this.body.binary();
|
||||
} catch {}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
export interface HttpLibrary {
|
||||
send(request: RequestContext): Observable<ResponseContext>;
|
||||
}
|
||||
|
||||
export interface PromiseHttpLibrary {
|
||||
send(request: RequestContext): Promise<ResponseContext>;
|
||||
}
|
||||
|
||||
export function wrapHttpLibrary(promiseHttpLibrary: PromiseHttpLibrary): HttpLibrary {
|
||||
return {
|
||||
send(request: RequestContext): Observable<ResponseContext> {
|
||||
return from(promiseHttpLibrary.send(request));
|
||||
}
|
||||
}
|
||||
}
|
||||
56
templates/http/isomorphic-fetch.mustache
Normal file
56
templates/http/isomorphic-fetch.mustache
Normal file
@@ -0,0 +1,56 @@
|
||||
import {HttpLibrary, RequestContext, ResponseContext} from './http{{extensionForDeno}}';
|
||||
import { from, Observable } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'../rxjsStub{{extensionForDeno}}'{{/useRxJS}};
|
||||
{{#platforms}}
|
||||
{{#node}}
|
||||
import fetch from "node-fetch";
|
||||
{{/node}}
|
||||
{{#browser}}
|
||||
import "whatwg-fetch";
|
||||
{{/browser}}
|
||||
{{/platforms}}
|
||||
|
||||
export class IsomorphicFetchHttpLibrary implements HttpLibrary {
|
||||
|
||||
public send(request: RequestContext): Observable<ResponseContext> {
|
||||
let method = request.getHttpMethod().toString();
|
||||
let body = request.getBody();
|
||||
|
||||
const resultPromise = fetch(request.getUrl(), {
|
||||
method: method,
|
||||
body: body as any,
|
||||
headers: request.getHeaders(),
|
||||
{{#platforms}}
|
||||
{{#node}}
|
||||
agent: request.getAgent(),
|
||||
{{/node}}
|
||||
{{#browser}}
|
||||
credentials: "same-origin"
|
||||
{{/browser}}
|
||||
{{/platforms}}
|
||||
}).then((resp: any) => {
|
||||
const headers: { [name: string]: string } = {};
|
||||
resp.headers.forEach((value: string, name: string) => {
|
||||
headers[name] = value;
|
||||
});
|
||||
|
||||
{{#platforms}}
|
||||
{{#node}}
|
||||
const body = {
|
||||
text: () => resp.text(),
|
||||
binary: () => resp.buffer()
|
||||
};
|
||||
{{/node}}
|
||||
{{^node}}
|
||||
const body = {
|
||||
text: () => resp.text(),
|
||||
binary: () => resp.blob()
|
||||
};
|
||||
{{/node}}
|
||||
{{/platforms}}
|
||||
return new ResponseContext(resp.status, headers, body);
|
||||
});
|
||||
|
||||
return from<Promise<ResponseContext>>(resultPromise);
|
||||
|
||||
}
|
||||
}
|
||||
86
templates/http/jquery.mustache
Normal file
86
templates/http/jquery.mustache
Normal file
@@ -0,0 +1,86 @@
|
||||
import { HttpLibrary, RequestContext, ResponseContext, HttpException, SelfDecodingBody } from './http';
|
||||
import * as e6p from 'es6-promise'
|
||||
import { from, Observable } from {{#useRxJS}}'rxjs'{{/useRxJS}}{{^useRxJS}}'../rxjsStub'{{/useRxJS}};
|
||||
e6p.polyfill();
|
||||
import * as $ from 'jquery';
|
||||
|
||||
|
||||
export class JQueryHttpLibrary implements HttpLibrary {
|
||||
|
||||
public send(request: RequestContext): Observable<ResponseContext> {
|
||||
let method = request.getHttpMethod().toString();
|
||||
let body = request.getBody();
|
||||
let headerParams = request.getHeaders()
|
||||
|
||||
let requestOptions: any = {
|
||||
url: request.getUrl(),
|
||||
type: method,
|
||||
headers: request.getHeaders(),
|
||||
processData: false,
|
||||
xhrFields: { withCredentials: true },
|
||||
data: body
|
||||
};
|
||||
|
||||
// If we want a blob, we have to set the xhrFields' responseType AND add a
|
||||
// custom converter to overwrite the default deserialization of JQuery...
|
||||
requestOptions["xhrFields"] = { responseType: 'blob' };
|
||||
requestOptions["converters"] = {}
|
||||
requestOptions["converters"]["* blob"] = (result:any) => result;
|
||||
requestOptions["dataType"] = "blob";
|
||||
|
||||
if (request.getHeaders()['Content-Type']) {
|
||||
requestOptions.contentType = headerParams['Content-Type'];
|
||||
}
|
||||
requestOptions.dataFilter = ((headerParams: { [key:string]: string}) => {
|
||||
return (data: string, type: string) => {
|
||||
if (headerParams["Accept"] == "application/json" && data == "") {
|
||||
return "{}"
|
||||
} else {
|
||||
return data
|
||||
}
|
||||
}
|
||||
})(headerParams);
|
||||
|
||||
if (request.getHeaders()["Cookie"]) {
|
||||
throw new HttpException("Setting the \"Cookie\"-Header field is blocked by every major browser when using jquery.ajax requests. Please switch to another library like fetch to enable this option");
|
||||
}
|
||||
|
||||
if (body && body.constructor.name == "FormData") {
|
||||
requestOptions.contentType = false;
|
||||
}
|
||||
|
||||
const sentRequest = $.ajax(requestOptions);
|
||||
|
||||
const resultPromise = new Promise<ResponseContext>((resolve, reject) => {
|
||||
sentRequest.done((data, _, jqXHR) => {
|
||||
const result = new ResponseContext(
|
||||
jqXHR.status,
|
||||
this.getResponseHeaders(jqXHR),
|
||||
new SelfDecodingBody(Promise.resolve(data))
|
||||
);
|
||||
resolve(result);
|
||||
})
|
||||
sentRequest.fail((jqXHR: any) => {
|
||||
const headers = this.getResponseHeaders(jqXHR)
|
||||
const result = new ResponseContext(jqXHR.status, headers, jqXHR.responseText);
|
||||
resolve(result);
|
||||
})
|
||||
})
|
||||
return from(resultPromise);
|
||||
}
|
||||
|
||||
private getResponseHeaders(jqXHR: any): { [key: string]: string } {
|
||||
const responseHeaders: { [key: string]: string } = {};
|
||||
var headers = jqXHR.getAllResponseHeaders();
|
||||
headers = headers.split("\n");
|
||||
headers.forEach(function (header: any) {
|
||||
header = header.split(": ");
|
||||
var key = header.shift();
|
||||
if (key.length == 0) return
|
||||
// chrome60+ force lowercase, other browsers can be different
|
||||
key = key.toLowerCase();
|
||||
responseHeaders[key] = header.join(": ");
|
||||
});
|
||||
return responseHeaders
|
||||
}
|
||||
}
|
||||
55
templates/http/servers.mustache
Normal file
55
templates/http/servers.mustache
Normal file
@@ -0,0 +1,55 @@
|
||||
import { RequestContext, HttpMethod } from "./http/http{{extensionForDeno}}";
|
||||
|
||||
export interface BaseServerConfiguration {
|
||||
makeRequestContext(endpoint: string, httpMethod: HttpMethod): RequestContext;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Represents the configuration of a server including its
|
||||
* url template and variable configuration based on the url.
|
||||
*
|
||||
*/
|
||||
export class ServerConfiguration<T extends { [key: string]: string }> implements BaseServerConfiguration {
|
||||
public constructor(private url: string, private variableConfiguration: T) {}
|
||||
|
||||
/**
|
||||
* Sets the value of the variables of this server.
|
||||
*
|
||||
* @param variableConfiguration a partial variable configuration for the variables contained in the url
|
||||
*/
|
||||
public setVariables(variableConfiguration: Partial<T>) {
|
||||
Object.assign(this.variableConfiguration, variableConfiguration);
|
||||
}
|
||||
|
||||
public getConfiguration(): T {
|
||||
return this.variableConfiguration
|
||||
}
|
||||
|
||||
private getUrl() {
|
||||
let replacedUrl = this.url;
|
||||
for (const key in this.variableConfiguration) {
|
||||
var re = new RegExp("{" + key + "}","g");
|
||||
replacedUrl = replacedUrl.replace(re, this.variableConfiguration[key]);
|
||||
}
|
||||
return replacedUrl
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new request context for this server using the url with variables
|
||||
* replaced with their respective values and the endpoint of the request appended.
|
||||
*
|
||||
* @param endpoint the endpoint to be queried on the server
|
||||
* @param httpMethod httpMethod to be used
|
||||
*
|
||||
*/
|
||||
public makeRequestContext(endpoint: string, httpMethod: HttpMethod): RequestContext {
|
||||
return new RequestContext(this.getUrl() + endpoint, httpMethod);
|
||||
}
|
||||
}
|
||||
|
||||
{{#servers}}
|
||||
export const server{{-index}} = new ServerConfiguration<{ {{#variables}} "{{name}}": {{#enumValues}}"{{.}}"{{^-last}} | {{/-last}}{{/enumValues}}{{^enumValues}}string{{/enumValues}}{{^-last}},{{/-last}} {{/variables}} }>("{{url}}", { {{#variables}} "{{name}}": "{{defaultValue}}" {{^-last}},{{/-last}}{{/variables}} })
|
||||
{{/servers}}
|
||||
|
||||
export const servers = [{{#servers}}server{{-index}}{{^-last}}, {{/-last}}{{/servers}}];
|
||||
Reference in New Issue
Block a user