mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[PRODENG-211] integrate JS-API with monorepo (part 1) (#9081)
* integrate JS-API with monorepo * [ci:force] fix token issue [ci:force] migrate docs folder [ci:force] clean personal tokens * [ci:force] gha workflow support * [ci:force] npm publish target * fix js-api test linting * [ci:force] fix test linting, mocks, https scheme * [ci:force] fix https scheme * [ci:force] typescript mappings * [ci:force] update scripts * lint fixes * linting fixes * fix linting * [ci:force] linting fixes * linting fixes * [ci:force] remove js-api upstream and corresponding scripts * [ci:force] jsdoc fixes * fix jsdoc linting * [ci:force] jsdoc fixes * [ci:force] jsdoc fixes * jsdoc fixes * jsdoc fixes * jsdoc fixes * [ci:force] fix jsdoc * [ci:force] reduce code duplication * replace 'chai' expect with node.js assert * replace 'chai' expect with node.js assert * [ci:force] remove chai and chai-spies for js-api testing * [ci:force] cleanup and fix imports * [ci:force] fix linting * [ci:force] fix unit test * [ci:force] fix sonar linting findings * [ci:force] switch activiti api models to interfaces (-2.5% reduction of bundle) * [ci:force] switch activiti api models to interfaces * [ci:force] switch AGS api models to interfaces * [ci:force] switch AGS api models to interfaces * [ci:force] switch search api models to interfaces * [ci:force] switch content api models to interfaces where applicable
This commit is contained in:
396
lib/js-api/src/alfrescoApiClient.ts
Normal file
396
lib/js-api/src/alfrescoApiClient.ts
Normal file
@@ -0,0 +1,396 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import ee from 'event-emitter';
|
||||
import { AlfrescoApiConfig } from './alfrescoApiConfig';
|
||||
import { Authentication } from './authentication/authentication';
|
||||
import { SuperagentHttpClient } from './superagentHttpClient';
|
||||
import { Emitters, HttpClient, LegacyHttpClient, RequestOptions, SecurityOptions } from './api-clients/http-client.interface';
|
||||
import { paramToString } from './utils';
|
||||
import { Storage } from './storage';
|
||||
|
||||
declare const Buffer: any;
|
||||
|
||||
export type AlfrescoApiClientPromise<T = any> = Promise<T> & {
|
||||
on: ee.EmitterMethod;
|
||||
off: ee.EmitterMethod;
|
||||
once: ee.EmitterMethod;
|
||||
emit: (type: string, ...args: any[]) => void;
|
||||
abort?: () => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds a string representation of an array-type actual parameter, according to the given collection format.
|
||||
*
|
||||
* @param param An array parameter.
|
||||
* @param collectionFormat The array element separator strategy.
|
||||
* @returns A string representation of the supplied collection, using the specified delimiter. Returns
|
||||
* <code>param</code> as is if <code>collectionFormat</code> is <code>multi</code>.
|
||||
*/
|
||||
export function buildCollectionParam(param: string[], collectionFormat: string): string | any[] {
|
||||
if (!param) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (collectionFormat) {
|
||||
case 'csv':
|
||||
return param.map(paramToString).join(',');
|
||||
case 'ssv':
|
||||
return param.map(paramToString).join(' ');
|
||||
case 'tsv':
|
||||
return param.map(paramToString).join('\t');
|
||||
case 'pipes':
|
||||
return param.map(paramToString).join('|');
|
||||
case 'multi':
|
||||
// return the array directly as SuperAgent will handle it as expected
|
||||
return param.map(paramToString);
|
||||
default:
|
||||
throw new Error('Unknown collection format: ' + collectionFormat);
|
||||
}
|
||||
}
|
||||
|
||||
export class AlfrescoApiClient implements ee.Emitter, LegacyHttpClient {
|
||||
on: ee.EmitterMethod;
|
||||
off: ee.EmitterMethod;
|
||||
once: ee.EmitterMethod;
|
||||
emit: (type: string, ...args: any[]) => void;
|
||||
|
||||
storage: Storage;
|
||||
host: string;
|
||||
className: string;
|
||||
config: AlfrescoApiConfig;
|
||||
url: string;
|
||||
/**
|
||||
* The base URL against which to resolve every API call's (relative) path.
|
||||
*/
|
||||
basePath = '';
|
||||
|
||||
/**
|
||||
* The authentication methods to be included for all API calls.
|
||||
*/
|
||||
authentications: Authentication = {
|
||||
basicAuth: {
|
||||
ticket: ''
|
||||
},
|
||||
type: 'basic'
|
||||
};
|
||||
/**
|
||||
* The default HTTP headers to be included for all API calls.
|
||||
*/
|
||||
defaultHeaders = {};
|
||||
|
||||
/**
|
||||
* The default HTTP timeout for all API calls.
|
||||
*/
|
||||
timeout: number | { deadline?: number; response?: number } = undefined;
|
||||
|
||||
contentTypes = {
|
||||
JSON: ['application/json']
|
||||
};
|
||||
|
||||
httpClient: HttpClient;
|
||||
|
||||
constructor(host?: string, httpClient?: HttpClient) {
|
||||
this.host = host;
|
||||
|
||||
this.storage = Storage.getInstance();
|
||||
|
||||
// fallback for backward compatibility
|
||||
this.httpClient = httpClient || new SuperagentHttpClient();
|
||||
|
||||
ee(this);
|
||||
}
|
||||
|
||||
request<T = any>(options: RequestOptions): Promise<T> {
|
||||
return this.buildRequestCall(this.basePath, options, this.httpClient.request.bind(this.httpClient));
|
||||
}
|
||||
|
||||
post<T = any>(options: RequestOptions): AlfrescoApiClientPromise<T> {
|
||||
const url = this.getCallApiUrl(options);
|
||||
return this.buildRequestCall(url, options, this.httpClient.post.bind(this.httpClient));
|
||||
}
|
||||
|
||||
put<T = any>(options: RequestOptions): AlfrescoApiClientPromise<T> {
|
||||
const url = this.getCallApiUrl(options);
|
||||
return this.buildRequestCall(url, options, this.httpClient.put.bind(this.httpClient));
|
||||
}
|
||||
|
||||
get<T = any>(options: RequestOptions): AlfrescoApiClientPromise<T> {
|
||||
const url = this.getCallApiUrl(options);
|
||||
return this.buildRequestCall(url, options, this.httpClient.get.bind(this.httpClient));
|
||||
}
|
||||
|
||||
delete<T = void>(options: RequestOptions): AlfrescoApiClientPromise<T> {
|
||||
const url = this.getCallApiUrl(options);
|
||||
return this.buildRequestCall<T>(url, options, this.httpClient.delete.bind(this.httpClient));
|
||||
}
|
||||
|
||||
callApi(
|
||||
path: string,
|
||||
httpMethod: string,
|
||||
pathParams?: any,
|
||||
queryParams?: any,
|
||||
headerParams?: any,
|
||||
formParams?: any,
|
||||
bodyParam?: any,
|
||||
contentTypes?: string[],
|
||||
accepts?: string[],
|
||||
returnType?: any,
|
||||
contextRoot?: string,
|
||||
responseType?: string,
|
||||
url?: string
|
||||
): AlfrescoApiClientPromise<any> {
|
||||
const callApiUrl = url ?? this.getCallApiUrl({ contextRoot, path, pathParams });
|
||||
|
||||
const options: RequestOptions = {
|
||||
path,
|
||||
httpMethod,
|
||||
pathParams,
|
||||
queryParams,
|
||||
headerParams,
|
||||
formParams,
|
||||
bodyParam,
|
||||
contentTypes,
|
||||
accepts,
|
||||
returnType,
|
||||
contextRoot,
|
||||
responseType,
|
||||
url
|
||||
};
|
||||
|
||||
return this.buildRequestCall(callApiUrl, options, this.httpClient.request.bind(this.httpClient));
|
||||
}
|
||||
|
||||
callCustomApi(
|
||||
path: string,
|
||||
httpMethod: string,
|
||||
pathParams?: any,
|
||||
queryParams?: any,
|
||||
headerParams?: any,
|
||||
formParams?: any,
|
||||
bodyParam?: any,
|
||||
contentTypes?: string[],
|
||||
accepts?: string[],
|
||||
returnType?: any,
|
||||
contextRoot?: string,
|
||||
responseType?: string
|
||||
): AlfrescoApiClientPromise<any> {
|
||||
const customApiUrl = AlfrescoApiClient.buildUrl(path, '', pathParams);
|
||||
const options: RequestOptions = {
|
||||
path,
|
||||
httpMethod,
|
||||
pathParams,
|
||||
queryParams,
|
||||
headerParams,
|
||||
formParams,
|
||||
bodyParam,
|
||||
contentTypes,
|
||||
accepts,
|
||||
returnType,
|
||||
contextRoot,
|
||||
responseType
|
||||
};
|
||||
|
||||
return this.buildRequestCall(customApiUrl, options, this.httpClient.request.bind(this.httpClient));
|
||||
}
|
||||
|
||||
isCsrfEnabled(): boolean {
|
||||
if (this.config) {
|
||||
return !this.config.disableCsrf;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
isBpmRequest(): boolean {
|
||||
return this.className === 'ProcessAuth' || this.className === 'ProcessClient';
|
||||
}
|
||||
|
||||
basicAuth(username: string, password: string): string {
|
||||
const str = username + ':' + password;
|
||||
|
||||
let base64;
|
||||
|
||||
if (typeof Buffer === 'function') {
|
||||
base64 = Buffer.from(str, 'binary').toString('base64');
|
||||
} else {
|
||||
base64 = btoa(str);
|
||||
}
|
||||
|
||||
return 'Basic ' + base64;
|
||||
}
|
||||
|
||||
isWithCredentials(): boolean {
|
||||
return !!this.config?.withCredentials;
|
||||
}
|
||||
|
||||
getAlfTicket(ticket: string): string {
|
||||
const ticketParam = this.isWithCredentials() ? '&ticket=' : '&alf_ticket=';
|
||||
|
||||
if (ticket) {
|
||||
return ticketParam + ticket;
|
||||
} else if (this.config.ticketEcm) {
|
||||
return ticketParam + this.config.ticketEcm;
|
||||
} else if (this.storage.getItem('ticket-ECM')) {
|
||||
return ticketParam + this.storage.getItem('ticket-ECM');
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds full URL by appending the given path to the base URL and replacing path parameter place-holders
|
||||
* with parameter values
|
||||
*/
|
||||
private static buildUrl(basePath: string, path: string, pathParams: any): string {
|
||||
if (path && path !== '' && !path.match(/^\//)) {
|
||||
path = '/' + path;
|
||||
}
|
||||
const url = basePath + path;
|
||||
|
||||
return AlfrescoApiClient.addParamsToUrl(url, pathParams);
|
||||
}
|
||||
|
||||
private static addParamsToUrl(path: string, pathParams: any) {
|
||||
return path.replace(/\{([\w-]+)}/g, (fullMatch, key) => {
|
||||
let value;
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(pathParams, key)) {
|
||||
value = paramToString(pathParams[key]);
|
||||
} else {
|
||||
value = fullMatch;
|
||||
}
|
||||
|
||||
return encodeURIComponent(value);
|
||||
});
|
||||
}
|
||||
|
||||
private getCallApiUrl({ contextRoot, path, pathParams }: { contextRoot?: string; path: string; pathParams?: any }): string {
|
||||
const basePath = contextRoot ? `${this.host}/${contextRoot}` : this.basePath;
|
||||
|
||||
return AlfrescoApiClient.buildUrl(basePath, path, pathParams);
|
||||
}
|
||||
|
||||
private buildRequestCall<T = any>(
|
||||
url: string,
|
||||
options: RequestOptions,
|
||||
httpCall: (url: string, options: RequestOptions, security: SecurityOptions, emitters: Emitters) => Promise<T>
|
||||
): AlfrescoApiClientPromise<T> {
|
||||
const security = this.getSecurityOptions();
|
||||
const emitters = this.getEventEmitters();
|
||||
const httpRequestOptions = this.getRequestOptionsWithAcceptAndContentType(options);
|
||||
const promise = httpCall(url, httpRequestOptions, security, emitters);
|
||||
|
||||
return this.addPromiseListeners(promise, emitters.eventEmitter);
|
||||
}
|
||||
|
||||
private getSecurityOptions(): SecurityOptions {
|
||||
return {
|
||||
isBpmRequest: this.isBpmRequest(),
|
||||
enableCsrf: this.isCsrfEnabled(),
|
||||
withCredentials: this.isWithCredentials(),
|
||||
authentications: this.authentications,
|
||||
defaultHeaders: this.defaultHeaders
|
||||
};
|
||||
}
|
||||
|
||||
private getEventEmitters(): Emitters {
|
||||
const apiClientEmitter = {
|
||||
on: this.on.bind(this),
|
||||
off: this.off.bind(this),
|
||||
once: this.once.bind(this),
|
||||
emit: this.emit.bind(this)
|
||||
};
|
||||
|
||||
return {
|
||||
apiClientEmitter,
|
||||
eventEmitter: ee({})
|
||||
};
|
||||
}
|
||||
|
||||
private getRequestOptionsWithAcceptAndContentType(options: RequestOptions): RequestOptions {
|
||||
const contentType = AlfrescoApiClient.jsonPreferredMime(options.contentTypes);
|
||||
const accept = AlfrescoApiClient.jsonPreferredMime(options.accepts);
|
||||
|
||||
return {
|
||||
...options,
|
||||
contentType,
|
||||
accept
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Chooses a content type from the given array, with JSON preferred; i.e. return JSON if included, otherwise return the first.
|
||||
*
|
||||
* @param contentTypes content types
|
||||
* @returns The chosen content type, preferring JSON.
|
||||
*/
|
||||
private static jsonPreferredMime(contentTypes: readonly string[]): string {
|
||||
if (!contentTypes?.length) {
|
||||
return 'application/json';
|
||||
}
|
||||
|
||||
for (const item of contentTypes) {
|
||||
if (AlfrescoApiClient.isJsonMime(item)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return contentTypes[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the given content type represents JSON.<br>
|
||||
*
|
||||
* JSON content type examples:<br>
|
||||
* <ul>
|
||||
* <li>application/json</li>
|
||||
* <li>application/json; charset=UTF8</li>
|
||||
* <li>APPLICATION/JSON</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param contentType The MIME content type to check.
|
||||
* @returns <code>true</code> if <code>contentType</code> represents JSON, otherwise <code>false</code>.
|
||||
*/
|
||||
private static isJsonMime(contentType: string): boolean {
|
||||
return Boolean(contentType?.match(/^application\/json(;.*)?$/i));
|
||||
}
|
||||
|
||||
private addPromiseListeners<T = any>(promise: Promise<T>, eventEmitter: ee.Emitter): AlfrescoApiClientPromise<T> {
|
||||
return Object.assign(promise, {
|
||||
on() {
|
||||
// eslint-disable-next-line prefer-spread,prefer-rest-params
|
||||
eventEmitter.on.apply(eventEmitter, arguments);
|
||||
return this;
|
||||
},
|
||||
once() {
|
||||
// eslint-disable-next-line prefer-spread,prefer-rest-params
|
||||
eventEmitter.once.apply(eventEmitter, arguments);
|
||||
return this;
|
||||
},
|
||||
emit() {
|
||||
// eslint-disable-next-line prefer-spread,prefer-rest-params
|
||||
eventEmitter.emit.apply(eventEmitter, arguments);
|
||||
return this;
|
||||
},
|
||||
off() {
|
||||
// eslint-disable-next-line prefer-spread,prefer-rest-params
|
||||
eventEmitter.off.apply(eventEmitter, arguments);
|
||||
return this;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user