{{#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; 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 { 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((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 { 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 { try { return this.body.text(); } catch {} try { return this.body.binary(); } catch {} return Promise.resolve(undefined); } } export interface HttpLibrary { send(request: RequestContext): Observable; } export interface PromiseHttpLibrary { send(request: RequestContext): Promise; } export function wrapHttpLibrary(promiseHttpLibrary: PromiseHttpLibrary): HttpLibrary { return { send(request: RequestContext): Observable { return from(promiseHttpLibrary.send(request)); } } }