Cleanup of structure, documentation
parent
cdde8ce67a
commit
917961a570
|
|
@ -0,0 +1,354 @@
|
|||
/**
|
||||
* The interface of a client for the LibreCcm RESTful API.
|
||||
*/
|
||||
export interface LibreCcmApiClient {
|
||||
/**
|
||||
* Performs a `GET` request for the provided endpoint.
|
||||
* Implementations should throw an {@link ApiError} if an error
|
||||
* occurs.
|
||||
*
|
||||
* @param endpoint The API endpoint to use.
|
||||
* @param searchParams Optional search parameters.
|
||||
*/
|
||||
get(
|
||||
endpoint: string,
|
||||
searchParams?: Record<string, string>
|
||||
): Promise<ApiResponse>;
|
||||
/**
|
||||
* Performs a `POST` request for the provided endpoint.
|
||||
* Implementations should throw an {@link ApiError} if an error
|
||||
* occurs.
|
||||
*
|
||||
* @param endpoint The API endpoint to use.
|
||||
* @param body The request body.
|
||||
* @param searchParams Optional search parameters.
|
||||
*/
|
||||
post(
|
||||
endpoint: string,
|
||||
body: unknown,
|
||||
searchParams?: Record<string, string>
|
||||
): Promise<ApiResponse>;
|
||||
/**
|
||||
* Performs a `PUT` request for the provided endpoint.
|
||||
* Implementations should throw an {@link ApiError} if an error
|
||||
* occurs.
|
||||
*
|
||||
* @param endpoint The API endpoint to use.
|
||||
* @param body The request body.
|
||||
* @param searchParams Optional search parameters.
|
||||
*/
|
||||
put(
|
||||
endpoint: string,
|
||||
body: Record<string, unknown>,
|
||||
searchParams?: Record<string, string>
|
||||
): Promise<ApiResponse>;
|
||||
/**
|
||||
* Performs a `DELETE` request for the provided endpoint.
|
||||
* Implementations should throw an {@link ApiError} if an error
|
||||
* occurs.
|
||||
*
|
||||
* @param endpoint The API endpoint to use.
|
||||
* @param searchParams Optional search parameters.
|
||||
*/
|
||||
delete(
|
||||
endpoint: string,
|
||||
searchParams?: Record<string, string>
|
||||
): Promise<ApiResponse>;
|
||||
/**
|
||||
* Performs a `HEAD` request for the provided endpoint.
|
||||
* Implementations should throw an {@link ApiError} if an error
|
||||
* occurs.
|
||||
*
|
||||
* @param endpoint The API endpoint to use.
|
||||
*/
|
||||
head(endpoint: string): Promise<ApiResponse>;
|
||||
/**
|
||||
* Performs a `OPTIONS` request for the provided endpoint.
|
||||
* Implementations should throw an {@link ApiError} if an error
|
||||
* occurs.
|
||||
*
|
||||
* @param endpoint The API endpoint to use.
|
||||
*/
|
||||
options(endpoint: string): Promise<ApiResponse>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The response from the API.
|
||||
*/
|
||||
export interface ApiResponse {
|
||||
/**
|
||||
* The HTTP status code returned by the RESTful API.
|
||||
*/
|
||||
status: number;
|
||||
/**
|
||||
* The status text returned by the RESTful API.
|
||||
*/
|
||||
statusText: string;
|
||||
/**
|
||||
* Gets the Response Body as JSON.
|
||||
*/
|
||||
json(): Promise<Record<string, unknown>>;
|
||||
/**
|
||||
* Gets the Response Body as ArrayBuffer.
|
||||
*/
|
||||
arrayBuffer(): Promise<ArrayBuffer>;
|
||||
/**
|
||||
* Gets the Response Body as `string`.
|
||||
*/
|
||||
text(): Promise<string>;
|
||||
}
|
||||
|
||||
export interface ApiError {
|
||||
/**
|
||||
* The HTTP status code reported by the API. `-1` if no status is available.
|
||||
*/
|
||||
status: number;
|
||||
/**
|
||||
* The status text reported by the API.
|
||||
*/
|
||||
statusText: string;
|
||||
/**
|
||||
* The HTTP method used for the failed request.
|
||||
*/
|
||||
method: "get" | "post" | "put" | "delete" | "head" | "option";
|
||||
/**
|
||||
* A error message providing (additional) details about the error.
|
||||
*/
|
||||
message: string;
|
||||
/**
|
||||
* The URL used in the failed request.
|
||||
*/
|
||||
url: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function for building an URL. Because it might be useful for implementing
|
||||
* client for specific APIs in the exported from this module.
|
||||
*
|
||||
* @param base The base URL pointing the an LibreCCM installation.
|
||||
* @param endpoint The endpoint to address.
|
||||
* @param searchParams Optional search parameter to append to the URL.
|
||||
*/
|
||||
export function buildUrl(
|
||||
base: string,
|
||||
endpoint: string,
|
||||
searchParams?: Record<string, string>
|
||||
): string {
|
||||
const url = new URL(base);
|
||||
url.pathname = endpoint;
|
||||
if (searchParams) {
|
||||
const urlSearchParams: URLSearchParams = new URLSearchParams();
|
||||
for (const key in searchParams) {
|
||||
urlSearchParams.append(key, searchParams[key]);
|
||||
}
|
||||
url.search = urlSearchParams.toString();
|
||||
}
|
||||
|
||||
return url.href;
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of the {@link ApiResponse} for the Fetch-API supported
|
||||
* by browsers.
|
||||
*/
|
||||
class FetchResponse implements ApiResponse {
|
||||
readonly status: number;
|
||||
readonly statusText: string;
|
||||
#response: Response;
|
||||
|
||||
constructor(response: Response) {
|
||||
this.status = response.status;
|
||||
this.statusText = response.statusText;
|
||||
this.#response = response;
|
||||
}
|
||||
|
||||
json(): Promise<Record<string, unknown>> {
|
||||
return this.#response.json();
|
||||
}
|
||||
|
||||
arrayBuffer(): Promise<ArrayBuffer> {
|
||||
return this.#response.arrayBuffer();
|
||||
}
|
||||
|
||||
text(): Promise<string> {
|
||||
return this.#response.text();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of the {@link LibreCcmApiClient} interface
|
||||
* using the Fetch-API supported by browsers.
|
||||
*/
|
||||
export class ApiClientFetchImpl implements LibreCcmApiClient {
|
||||
readonly #baseUrl: string;
|
||||
readonly #fetchOptions: RequestInit;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param baseUrl The URL of the LibreCCM installation to use.
|
||||
* @param fetchOptions Basic fetch options for requests.
|
||||
*/
|
||||
constructor(baseUrl: string, fetchOptions: RequestInit) {
|
||||
this.#baseUrl = baseUrl;
|
||||
this.#fetchOptions = fetchOptions;
|
||||
}
|
||||
|
||||
private buildFetchOptions(
|
||||
method: "get" | "post" | "put" | "delete" | "head" | "options"
|
||||
): RequestInit {
|
||||
const fetchOptions: RequestInit = {};
|
||||
Object.assign(fetchOptions, this.#fetchOptions);
|
||||
fetchOptions.method = method;
|
||||
|
||||
return fetchOptions;
|
||||
}
|
||||
|
||||
async get(
|
||||
endpoint: string,
|
||||
searchParams?: Record<string, string>
|
||||
): Promise<ApiResponse> {
|
||||
const url = buildUrl(this.#baseUrl, endpoint, searchParams);
|
||||
try {
|
||||
const response = await fetch(url, this.buildFetchOptions("get"));
|
||||
if (response.ok) {
|
||||
return new FetchResponse(response);
|
||||
} else {
|
||||
throw {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
method: "get",
|
||||
message: "API responded with an error.",
|
||||
url,
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
throw {
|
||||
status: -1,
|
||||
statusText: "n/a",
|
||||
method: "get",
|
||||
message: `Failed to execute get: ${err}`,
|
||||
url,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
post(
|
||||
endpoint: string,
|
||||
body: unknown,
|
||||
searchParams?: Record<string, string>
|
||||
): Promise<ApiResponse> {
|
||||
throw "Not implemented yet.";
|
||||
}
|
||||
|
||||
put(
|
||||
endpoint: string,
|
||||
body: unknown,
|
||||
searchParams?: Record<string, string>
|
||||
): Promise<ApiResponse> {
|
||||
throw "Not implemented yet.";
|
||||
}
|
||||
|
||||
delete(
|
||||
endpoint: string,
|
||||
searchParams?: Record<string, string>
|
||||
): Promise<ApiResponse> {
|
||||
throw "Not implemented yet.";
|
||||
}
|
||||
|
||||
head(endpoint: string): Promise<ApiResponse> {
|
||||
throw "Not implemented yet.";
|
||||
}
|
||||
|
||||
options(endpoint: string): Promise<ApiResponse> {
|
||||
throw "Not implemented yet.";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of the {@link LibreCcmApiClient} interface using the HTTP API
|
||||
* provided by node.js.
|
||||
*/
|
||||
export class ApiClientNodeImpl implements LibreCcmApiClient {
|
||||
get(
|
||||
endpoint: string,
|
||||
searchParams?: Record<string, string>
|
||||
): Promise<ApiResponse> {
|
||||
throw "Not implemented yet.";
|
||||
}
|
||||
|
||||
post(
|
||||
endpoint: string,
|
||||
body: unknown,
|
||||
searchParams?: Record<string, string>
|
||||
): Promise<ApiResponse> {
|
||||
throw "Not implemented yet.";
|
||||
}
|
||||
|
||||
put(
|
||||
endpoint: string,
|
||||
body: unknown,
|
||||
searchParams?: Record<string, string>
|
||||
): Promise<ApiResponse> {
|
||||
throw "Not implemented yet.";
|
||||
}
|
||||
|
||||
delete(
|
||||
endpoint: string,
|
||||
searchParams?: Record<string, string>
|
||||
): Promise<ApiResponse> {
|
||||
throw "Not implemented yet.";
|
||||
}
|
||||
|
||||
head(endpoint: string): Promise<ApiResponse> {
|
||||
throw "Not implemented yet.";
|
||||
}
|
||||
|
||||
options(endpoint: string): Promise<ApiResponse> {
|
||||
throw "Not implemented yet.";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An isomorphic implementation of the {@link LibreCcmApiClient} interface.
|
||||
* Under the hood the implementation checks if the the Fetch-API is avaiable
|
||||
* and used either the {@link ApiClientFetchImpl} or the {@link ApiClientNodeImpl}.
|
||||
*/
|
||||
export class IsomorphicClientImpl implements LibreCcmApiClient {
|
||||
get(
|
||||
endpoint: string,
|
||||
searchParams?: Record<string, string>
|
||||
): Promise<ApiResponse> {
|
||||
throw "Not implemented yet.";
|
||||
}
|
||||
|
||||
post(
|
||||
endpoint: string,
|
||||
body: unknown,
|
||||
searchParams?: Record<string, string>
|
||||
): Promise<ApiResponse> {
|
||||
throw "Not implemented yet.";
|
||||
}
|
||||
|
||||
put(
|
||||
endpoint: string,
|
||||
body: unknown,
|
||||
searchParams?: Record<string, string>
|
||||
): Promise<ApiResponse> {
|
||||
throw "Not implemented yet.";
|
||||
}
|
||||
|
||||
delete(
|
||||
endpoint: string,
|
||||
searchParams?: Record<string, string>
|
||||
): Promise<ApiResponse> {
|
||||
throw "Not implemented yet.";
|
||||
}
|
||||
|
||||
head(endpoint: string): Promise<ApiResponse> {
|
||||
throw "Not implemented yet.";
|
||||
}
|
||||
|
||||
options(endpoint: string): Promise<ApiResponse> {
|
||||
throw "Not implemented yet.";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
export interface RequestInitProvider {
|
||||
buildRequestInit(): RequestInit;
|
||||
}
|
||||
|
||||
export class EmbeddedRequestInitProvider implements RequestInitProvider {
|
||||
buildRequestInit(): RequestInit {
|
||||
return {
|
||||
credentials: "include",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoteRequestInitProvider implements RequestInitProvider {
|
||||
#jwt: string;
|
||||
|
||||
constructor(jwt: string) {
|
||||
this.#jwt = jwt;
|
||||
}
|
||||
|
||||
buildRequestInit(): RequestInit {
|
||||
return {
|
||||
credentials: "omit",
|
||||
headers: {
|
||||
authorization: this.#jwt,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,58 +1,27 @@
|
|||
import http from "http";
|
||||
import { AnyARecord } from "dns";
|
||||
import {
|
||||
LibreCcmApiClient,
|
||||
ApiResponse,
|
||||
ApiError,
|
||||
buildUrl,
|
||||
ApiClientFetchImpl,
|
||||
ApiClientNodeImpl,
|
||||
IsomorphicClientImpl,
|
||||
} from "./ApiClient";
|
||||
|
||||
export * from "./entities";
|
||||
export * from "./RequestInitProvider";
|
||||
|
||||
export interface LibreCcmApiClient {
|
||||
get(
|
||||
endpoint: string,
|
||||
searchParams: Record<string, string>
|
||||
): Promise<ApiResponse>;
|
||||
post(
|
||||
endpoint: string,
|
||||
searchParams: Record<string, string>,
|
||||
body: unknown
|
||||
): Promise<Response>;
|
||||
put(endpoint: string, body: Record<string, unknown>): Promise<Response>;
|
||||
delete(endpoint: string): Promise<Response>;
|
||||
head(endpoint: string): Promise<Response>;
|
||||
options(endpoint: string): Promise<Response>;
|
||||
}
|
||||
|
||||
export interface ApiResponse {
|
||||
status: number;
|
||||
statusText: string;
|
||||
json(): Promise<Record<string, unknown>>;
|
||||
blob(): Promise<Blob>;
|
||||
text(): Promise<string>
|
||||
}
|
||||
|
||||
export interface ApiError {
|
||||
status: number;
|
||||
statusText: string;
|
||||
method: "get" | "post" | "put" | "delete" | "head" | "option";
|
||||
message: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export function buildUrl(base: string, endpoint: string, searchParams?: Record<string, string>): string {
|
||||
const url = new URL(base);
|
||||
url.pathname = endpoint;
|
||||
if (searchParams) {
|
||||
const urlSearchParams: URLSearchParams = new URLSearchParams();
|
||||
for (const key in searchParams) {
|
||||
urlSearchParams.append(key, searchParams[key]);
|
||||
}
|
||||
url.search = urlSearchParams.toString();
|
||||
}
|
||||
|
||||
return url.href;
|
||||
}
|
||||
export { LibreCcmApiClient, ApiResponse, ApiError, buildUrl };
|
||||
|
||||
/**
|
||||
* Build an client for the LibreCCM RESTful API suitable for use in
|
||||
* clientside code served by the Application Server.
|
||||
*
|
||||
* The base URL for accessing the LibreCCM RESTful API is automatically
|
||||
* determined from the URL of the current document. The API client will
|
||||
* use the credentials stored in the browser (cookies) to authenticate itself.
|
||||
*/
|
||||
export function buildEmbeddedApiClient(): LibreCcmApiClient {
|
||||
if (!document) {
|
||||
throw "document global variable is not available. Please use the buildRemoteApiClient.";
|
||||
if (!fetch) {
|
||||
throw "Fetch API is not available. Please use buildIsomorpicApiClient.";
|
||||
}
|
||||
const baseUrl = new URL(document.documentURI);
|
||||
baseUrl.hash = "";
|
||||
|
|
@ -61,95 +30,58 @@ export function buildEmbeddedApiClient(): LibreCcmApiClient {
|
|||
baseUrl.search = "";
|
||||
baseUrl.username = "";
|
||||
|
||||
return new EmbeddedApiClient(baseUrl.href, {
|
||||
return new ApiClientFetchImpl(baseUrl.href, {
|
||||
credentials: "include",
|
||||
mode: "same-origin",
|
||||
});
|
||||
}
|
||||
|
||||
class FetchResponse implements ApiResponse {
|
||||
readonly status: number;
|
||||
readonly statusText: string;
|
||||
#response: Response;
|
||||
|
||||
constructor(response: Response) {
|
||||
this.status = response.status;
|
||||
this.statusText = response.statusText;
|
||||
this.#response = response;
|
||||
/**
|
||||
* Builds a client for the LibreCCM RESTful API suitable for running in a browser.
|
||||
*
|
||||
* @param baseUrl The URL of the LibreCCM installation to access, including the port.
|
||||
* @param jwt The JSON Web Token to use by the client to authenticate itself.
|
||||
*/
|
||||
export function buildRemoteApiClient(
|
||||
baseUrl: string,
|
||||
jwt: string
|
||||
): LibreCcmApiClient {
|
||||
if (!fetch) {
|
||||
throw "Fetch API is not available. Please use buildIsomorpicApiClient.";
|
||||
}
|
||||
|
||||
json(): Promise<Record<string, unknown>> {
|
||||
return this.#response.json();
|
||||
}
|
||||
|
||||
blob(): Promise<Blob> {
|
||||
return this.#response.blob();
|
||||
}
|
||||
|
||||
text(): Promise<string> {
|
||||
return this.#response.text();
|
||||
}
|
||||
return new ApiClientFetchImpl(baseUrl, {
|
||||
headers: {
|
||||
Authorization: jwt,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
class EmbeddedApiClient implements LibreCcmApiClient {
|
||||
#baseUrl: string;
|
||||
#fetchOptions: RequestInit;
|
||||
|
||||
constructor(baseUrl: string, fetchOptions: RequestInit) {
|
||||
this.#baseUrl = baseUrl;
|
||||
this.#fetchOptions = fetchOptions;
|
||||
}
|
||||
|
||||
async get(
|
||||
endpoint: string,
|
||||
searchParams?: Record<string, string>
|
||||
): Promise<ApiResponse> {
|
||||
const fetchOptions: RequestInit = {};
|
||||
Object.assign(fetchOptions, this.#fetchOptions);
|
||||
fetchOptions.method = "get";
|
||||
|
||||
const url = buildUrl(this.#baseUrl, endpoint, searchParams);
|
||||
try {
|
||||
const response = await fetch(url, this.#fetchOptions);
|
||||
if (response.ok) {
|
||||
return new FetchResponse(response);
|
||||
} else {
|
||||
throw {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
method: "get",
|
||||
message: "Received an error from the API endpoint. ",
|
||||
url
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
throw `Failed to execute GET on ${url}: ${err}`;
|
||||
}
|
||||
}
|
||||
|
||||
async post(
|
||||
endpoint: string,
|
||||
searchParams: Record<string, string>,
|
||||
body: unknown
|
||||
): Promise<Response> {
|
||||
throw "Not implemented yet";
|
||||
}
|
||||
|
||||
put(endpoint: string, body: Record<string, unknown>): Promise<Response> {
|
||||
throw "Not implemented yet";
|
||||
}
|
||||
|
||||
delete(endpoint: string): Promise<Response> {
|
||||
throw "Not implemented yet";
|
||||
}
|
||||
|
||||
head(endpoint: string): Promise<Response> {
|
||||
throw "Not implemented yet";
|
||||
}
|
||||
|
||||
options(endpoint: string): Promise<Response> {
|
||||
throw "Not implemented yet";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builds a client for the LibreCCM RESTful API suitable for running inside a node.js
|
||||
* environment.
|
||||
*
|
||||
* @param baseUrl The URL of the LibreCCM installation to access, including the port.
|
||||
* @param jwt The JSON Web Token to use by the client to authenticate itself.
|
||||
*/
|
||||
export function buildNodeApiClient(
|
||||
baseUrl: string,
|
||||
jwt: string
|
||||
): LibreCcmApiClient {
|
||||
return new ApiClientNodeImpl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an isomorphic client for the LibreCCM RESTful API which will work in the
|
||||
* browser and in a node.js environment. Use this function to create an API client
|
||||
* for JavaScript applications which use Server-Side-Rendering.
|
||||
*
|
||||
* @param baseUrl The URL of the LibreCCM installation to access, including the port.
|
||||
* @param jwt The JSON Web Token to use by the client to authenticate itself.
|
||||
*/
|
||||
export function buildIsomorpicApiClient(
|
||||
baseUrl: string,
|
||||
jwt: string
|
||||
): LibreCcmApiClient {
|
||||
return new IsomorphicClientImpl();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
/**
|
||||
* Properties of the type `LocalizedString` are used in several entities
|
||||
* returned by various of the endpoints the RESTful API of LibreCCM.
|
||||
* Each `LocalizedString` consists of various values with the language
|
||||
* as key.
|
||||
*/
|
||||
export interface LocalizedString {
|
||||
[language: string]: string;
|
||||
values: Record<string, string>
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue