PoC: Make @alfresco/js-api to be compatible with angular's http client

This commit is contained in:
Andras Popovics
2022-02-25 15:33:31 +01:00
parent a0c7631abb
commit e432ba6d37
14 changed files with 574 additions and 12 deletions

View File

@@ -0,0 +1,48 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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.
*/
/*tslint:disable*/ // => because of ADF file naming problems... Try to remove it, if you don't believe me :P
import { Injectable } from '@angular/core';
import { DiscoveryApi } from '@alfresco/js-api';
import { AngularAlfrescoApi } from './angular-alfresco-api';
@Injectable()
export class AlfrescoApiClientFactory {
// Here we should all the APIs from js-api
private discoveryApi: DiscoveryApi = null;
constructor(
private angularAlfrescoApi?: AngularAlfrescoApi) {
}
getDiscoveryApi() {
// DiscoveryApi needs to rely on a lot thinner interface: JsApiHttpClient;
this.discoveryApi = this.discoveryApi || new DiscoveryApi(this.angularAlfrescoApi as any);
return this.discoveryApi;
}
getNodesApi () {
// TODO
}
getSearchApi() {
// TODO
}
// etc...
}

View File

@@ -0,0 +1,65 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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 { Injectable } from '@angular/core';
import { AlfrescoApi, AlfrescoApiConfig } from '@alfresco/js-api';
import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service';
import { OauthConfigModel } from '../../models/oauth-config.model';
import { StorageService } from '../../services/storage.service';
import { AngularAlfrescoApi } from './angular-alfresco-api';
@Injectable()
export class AngularAlfrescoApiLoaderService {
protected alfrescoApi: AlfrescoApi;
constructor(
protected appConfig: AppConfigService,
protected storageService: StorageService,
private angularAlfrescoApi?: AngularAlfrescoApi) {
}
async load() {
await this.appConfig.load().then(() => {
this.storageService.prefix = this.appConfig.get<string>(AppConfigValues.STORAGE_PREFIX, '');
this.initAngularAlfrescoApi();
});
}
protected initAngularAlfrescoApi() {
const oauth: OauthConfigModel = Object.assign({}, this.appConfig.get<OauthConfigModel>(AppConfigValues.OAUTHCONFIG, null));
if (oauth) {
oauth.redirectUri = window.location.origin + window.location.pathname;
oauth.redirectUriLogout = window.location.origin + window.location.pathname;
}
const config = new AlfrescoApiConfig({
provider: this.appConfig.get<string>(AppConfigValues.PROVIDERS),
hostEcm: this.appConfig.get<string>(AppConfigValues.ECMHOST),
hostBpm: this.appConfig.get<string>(AppConfigValues.BPMHOST),
authType: this.appConfig.get<string>(AppConfigValues.AUTHTYPE, 'BASIC'),
contextRootBpm: this.appConfig.get<string>(AppConfigValues.CONTEXTROOTBPM),
contextRoot: this.appConfig.get<string>(AppConfigValues.CONTEXTROOTECM),
disableCsrf: this.appConfig.get<boolean>(AppConfigValues.DISABLECSRF),
withCredentials: this.appConfig.get<boolean>(AppConfigValues.AUTH_WITH_CREDENTIALS, false),
domainPrefix : this.appConfig.get<string>(AppConfigValues.STORAGE_PREFIX),
oauth2: oauth
});
this.angularAlfrescoApi.init(config);
}
}

View File

@@ -0,0 +1,85 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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 { AlfrescoApiConfig } from '@alfresco/js-api';
import { Injectable } from '@angular/core';
import { JsApiHttpClient } from '../js-api/js-api-http-client';
import { JsApiAngularHttpClient } from './js-api-angular-http-client';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class AngularAlfrescoApi {
public contentPrivateClient: JsApiHttpClient;
public contentClient: JsApiHttpClient;
public authClient: JsApiHttpClient;
public searchClient: JsApiHttpClient;
public discoveryClient: JsApiHttpClient;
public gsClient: JsApiHttpClient;
public processClient: JsApiHttpClient;
constructor(private httpClient: HttpClient) {}
init(config: AlfrescoApiConfig) {
this.contentPrivateClient = new JsApiAngularHttpClient(
config.hostEcm,
config.contextRoot,
`/api/${config.tenant}/private/alfresco/versions/1`,
this.httpClient
);
this.contentClient = new JsApiAngularHttpClient(
config.hostEcm,
config.contextRoot,
`/api/${config.tenant}/public/alfresco/versions/1`,
this.httpClient
);
this.authClient = new JsApiAngularHttpClient(
config.hostEcm,
config.contextRoot,
`/api/${config.tenant}/public/authentication/versions/1`,
this.httpClient
);
this.searchClient = new JsApiAngularHttpClient(
config.hostEcm,
config.contextRoot,
`/api/${config.tenant}/public/search/versions/1`,
this.httpClient
);
this.discoveryClient = new JsApiAngularHttpClient(
config.hostEcm,
config.contextRoot,
`/api`,
this.httpClient
);
this.gsClient = new JsApiAngularHttpClient(
config.hostEcm,
config.contextRoot,
`/api/${config.tenant}/public/gs/versions/1`,
this.httpClient
);
this.processClient = new JsApiAngularHttpClient(
config.hostBpm,
config.contextRootBpm,
'',
this.httpClient
);
}
}

View File

@@ -0,0 +1,23 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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 { AngularAlfrescoApiLoaderService } from './angular-alfresco-api-loader.service';
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
export function createAngularAlfrescoApiService(angularAlfrescoApiService: AngularAlfrescoApiLoaderService) {
return () => angularAlfrescoApiService.load();
}

View File

@@ -0,0 +1,79 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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 { throwError as observableThrowError, Observable } from 'rxjs';
import { Injectable, Injector } from '@angular/core';
import {
HttpHandler, HttpInterceptor, HttpRequest,
HttpSentEvent, HttpHeaderResponse, HttpProgressEvent, HttpResponse, HttpUserEvent, HttpHeaders
} from '@angular/common/http';
import { AuthenticationService } from '../../services/authentication.service';
import { catchError, mergeMap } from 'rxjs/operators';
@Injectable()
export class AuthBearerInterceptor implements HttpInterceptor {
private excludedUrlsRegex: RegExp[];
private authService: AuthenticationService;
constructor(private injector: Injector) { }
private loadExcludedUrlsRegex() {
const excludedUrls: string[] = this.authService.getBearerExcludedUrls();
this.excludedUrlsRegex = excludedUrls.map((urlPattern) => new RegExp(urlPattern, 'gi')) || [];
}
intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {
this.authService = this.injector.get(AuthenticationService);
if (!this.authService || !this.authService.getBearerExcludedUrls()) {
return next.handle(req);
}
if (!this.excludedUrlsRegex) {
this.loadExcludedUrlsRegex();
}
const urlRequest = req.url;
const shallPass: boolean = !!this.excludedUrlsRegex.find((regex) => regex.test(urlRequest));
if (shallPass) {
return next.handle(req)
.pipe(
catchError((error) => observableThrowError(error))
);
}
return this.authService.addTokenToHeader(req.headers)
.pipe(
mergeMap((headersWithBearer) => {
const headerWithContentType = this.appendJsonContentType(headersWithBearer);
const kcReq = req.clone({ headers: headerWithContentType});
return next.handle(kcReq)
.pipe(
catchError((error) => observableThrowError(error))
);
})
);
}
private appendJsonContentType(headers: HttpHeaders): HttpHeaders {
return headers.set('Content-Type', 'application/json;charset=UTF-8');
}
}

View File

@@ -0,0 +1,190 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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 {
paramToString,
RequestOptions
} from '@alfresco/js-api';
import { JsApiHttpClient } from '../js-api/js-api-http-client';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
/** tslint:disable-next line */
export class JsApiAngularHttpClient implements JsApiHttpClient {
public basePath: string;
constructor (
private host: string,
private contextRoot: string,
private servicePath: string,
private httpClient: HttpClient
) {
this.basePath = `${this.host}/${this.contextRoot}${this.servicePath}`;
}
request<T = any>(options: RequestOptions): Promise<T> {
const responseType = this.getResponseType(options);
return this.httpClient.request(
options.httpMethod,
options.url,
{
...(options.bodyParam ? { body: options.bodyParam } : {}),
...(options.headerParams ? { headers: new HttpHeaders(options.headerParams) } : {}),
observe: 'body',
...(options.queryParams ? { params: new HttpParams({ fromObject: options.queryParams })} : {}),
...(responseType ? { responseType } : {}),
}).toPromise() as unknown as Promise<T>;
}
private getResponseType(options: RequestOptions): 'arraybuffer' | 'blob' | 'json' | 'text' {
let responseType = null;
if (options.returnType?.toString().toLowerCase() === 'blob' || options.responseType?.toString().toLowerCase() === 'blob') {
responseType = 'blob';
} else if (options.returnType === 'String') {
responseType = 'text';
}
return responseType;
}
post<T = any>(options: RequestOptions): Promise<T> {
return this.request<T>({
...options,
httpMethod: 'POST',
contentTypes: options.contentTypes || ['application/json'],
accepts: options.accepts || ['application/json']
});
}
put<T = any>(options: RequestOptions): Promise<T> {
return this.request<T>({
...options,
httpMethod: 'PUT',
contentTypes: options.contentTypes || ['application/json'],
accepts: options.accepts || ['application/json']
});
}
get<T = any>(options: RequestOptions): Promise<T> {
return this.request<T>({
...options,
httpMethod: 'GET',
contentTypes: options.contentTypes || ['application/json'],
accepts: options.accepts || ['application/json']
});
}
delete<T = void>(options: RequestOptions): Promise<T> {
return this.request<T>({
...options,
httpMethod: 'DELETE',
contentTypes: options.contentTypes || ['application/json'],
accepts: options.accepts || ['application/json']
});
}
/** @deprecated */
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
): Promise<any> {
const basePath = contextRoot ? `${this.host}/${contextRoot}` : this.basePath;
url = url ?? this.buildUrl(basePath, path, pathParams);
return this.request({
path,
httpMethod,
pathParams,
queryParams,
headerParams,
formParams,
bodyParam,
contentTypes,
accepts,
returnType,
contextRoot,
responseType,
url
});
}
/** @deprecated */
callCustomApi(
fullPath: string,
httpMethod: string,
pathParams?: any,
queryParams?: any,
headerParams?: any,
formParams?: any,
bodyParam?: any,
contentTypes?: string[],
accepts?: string[],
returnType?: any,
contextRoot?: string,
responseType?: string
): Promise<any> {
const url = this.buildUrl(fullPath, '', pathParams);
return this.request({
path: fullPath,
httpMethod,
pathParams,
queryParams,
headerParams,
formParams,
bodyParam,
contentTypes,
accepts,
returnType,
contextRoot,
responseType,
url
});
}
private buildUrl(basePath: string, path: string, pathParams: any): string {
if (path && path !== '' && !path.match(/^\//)) {
path = '/' + path;
}
let url = basePath + path;
url = url.replace(/\{([\w-]+)\}/g, function (fullMatch, key) {
let value;
if (pathParams.hasOwnProperty(key)) {
value = paramToString(pathParams[key]);
} else {
value = fullMatch;
}
return encodeURIComponent(value);
});
return url;
}
}