@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
295 lines (286 loc) • 12.4 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, Optional, SkipSelf, runInInjectionContext, Injector, inject, provideAppInitializer, NgModule } from '@angular/core';
import * as i1 from '@c8y/client';
import { FetchClient, BasicAuth, CookieAuth, Realtime, EventBinaryService, EventService, InventoryService, MeasurementService, AlarmService, OperationBulkService, OperationService, ApplicationService, UserService, TenantService, SystemOptionsService, TenantOptionsService, TenantSecurityOptionsService, TenantLoginOptionsService, AuditService, InventoryRoleService, InventoryBinaryService, DeviceRegistrationService, DeviceRegistrationBulkService, UserRoleService, UserGroupService, IdentityService, TrustedCertificateService, CrlService, SmartGroupsService, SmartRulesService, FeatureService, BearerAuthFromSessionStorage } from '@c8y/client';
import { from, Subject, pipe } from 'rxjs';
import { filter, map, scan, distinctUntilChanged, shareReplay } from 'rxjs/operators';
import { pickAuthStrategy } from '@c8y/bootstrap';
class HttpHandler {
}
class HttpInterceptHandler extends HttpHandler {
constructor(interceptor, nextHandler) {
super();
this.interceptor = interceptor;
this.nextHandler = nextHandler;
}
handle(req) {
return this.interceptor.intercept(req, this.nextHandler);
}
}
class HttpRequestHandler extends HttpHandler {
constructor(fetch, apiService) {
super();
this.fetch = fetch;
this.apiService = apiService;
}
handle(req) {
const { options, url } = req;
const { method } = options;
this.apiService?.onStart({ method, options, url });
let fetchPromise = this.fetch(url, options);
if (typeof options.responseInterceptor === 'function') {
fetchPromise = fetchPromise.then(options.responseInterceptor);
}
fetchPromise.then((response) => this.apiService?.onFinish({ method, options, url, response }), (response) => this.apiService?.onFinish({ method, options, url, response }));
return from(fetchPromise);
}
}
class ApiService {
constructor(client) {
this.client = client;
this.callsSubject = new Subject();
this.interceptors = new Map();
this.interceptorCounter = 0;
this.calls = this.callsSubject.asObservable();
this.hookIntoClientFetch();
this.isLoading$ = this.calls.pipe(filter(({ url }) => !/notification\/realtime/.test(url)), map(({ phase }) => (phase === 'start' ? 1 : -1)), scan((count, item) => count + item, 0), map(count => count > 0), distinctUntilChanged(), shareReplay(1));
}
/**
* Allows to hook into the responses received by the FetchClient.
* This is meant to be used to react on the responses, not for manipulation of the responses.
* @param hookFilter A filter function to filter for specific responses.
* @returns An Observable of the filtered responses.
*/
hookResponse(hookFilter) {
return this.callsSubject.pipe(filter(({ phase }) => phase === 'finish'), filter(hookFilter));
}
/**
* Allows to hook into the requests performed by the FetchClient.
* This is meant to be used to react on the requests, not for manipulation of the requests.
* @param hookFilter A filter function to filter for specific requests.
* @returns An Observable of the filtered requests.
*/
hookRequest(hookFilter) {
return this.callsSubject.pipe(filter(({ phase }) => phase === 'start'), filter(hookFilter));
}
/**
* Notifies observers that an API call has finished.
* @param call The API call that has finished.
*/
onFinish(call) {
this.callsSubject.next({ phase: 'finish', ...call });
}
/**
* Notifies observers that an API call has started.
* @param call The API call that has started.
*/
onStart(call) {
this.callsSubject.next({ phase: 'start', ...call });
}
/**
* Resolves data from an API call response.
* @returns A Promise containing an object with resolved data, method, and URL.
*/
resolveData(call) {
const { response, method, url } = call;
if ('data' in response) {
return Promise.resolve({ data: response.data, method, url });
}
else {
// No Content success status, for example DELETE request.
if (response?.status === 204) {
return Promise.resolve({ data: null, method, url });
}
const cb = data => ({ data, method, url });
return response.clone().json().then(cb, cb);
}
}
/**
* Can be added to a pipe to exclude any permission call. Permission calls are PUT
* request with only an id in it, to verify if the user has access to this managed object.
* @returns The operator to be added to a pipe.
*/
excludePermissionCall() {
return pipe(filter(({ method, options }) => {
if (method === 'PUT' && options.body && typeof options.body === 'string') {
const parsedBody = JSON.parse(options.body);
const bodyKeys = Object.keys(parsedBody);
return !(bodyKeys.length === 1 && bodyKeys[0] === 'id');
}
return true;
}));
}
/**
* Allows to intercept requests performed via the FetchClient requests.
* @param interceptor The interceptor to be added.
* @param id An optional unique identifier for the interceptor. The chain of interceptors is ordered by this id. And it can be used to remove the interceptor later on.
* @returns The id of the interceptor (same as provided id if one was provided, otherwise an id will be generated).
*/
addInterceptor(interceptor, id) {
if (!id) {
id = `${++this.interceptorCounter}`;
}
this.interceptors.set(id, interceptor);
return id;
}
/**
* Allows to remove a previously added interceptor by it's id.
* @param id The id of the interceptor that should be removed.
* @returns true if an interceptor existed and has been removed, or false if id does not exist.
*/
removeInterceptor(id) {
return this.interceptors.delete(id);
}
/**
* Checks if an interceptor with a given id exists.
* @param id The id of the interceptor.
* @returns - Returns true if an interceptor with the given id exists, otherwise false.
*/
hasInterceptor(id) {
return this.interceptors.has(id);
}
hookIntoClientFetch() {
const fetch = this.client.fetch.bind(this.client);
const requestHandler = new HttpRequestHandler(fetch, this);
this.client.fetch = async (url, options = { method: 'GET' }) => {
const { method } = options;
return this.createInterceptorChain({ url, options, method }, requestHandler).toPromise();
};
}
createInterceptorChain(call, requestHandler) {
let handler = requestHandler;
// Do some sorting to always apply the interceptors in the specific order
const sortedInterceptorIds = Array.from(this.interceptors.keys()).sort((a, b) => b.localeCompare(a));
for (const interceptorId of sortedInterceptorIds) {
handler = new HttpInterceptHandler(this.interceptors.get(interceptorId), handler);
}
return handler.handle(call);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ApiService, deps: [{ token: i1.FetchClient }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ApiService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ApiService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [{ type: i1.FetchClient }] });
const defaultServicesFromClientLib = [
FetchClient,
BasicAuth,
CookieAuth,
Realtime,
EventBinaryService,
EventService,
InventoryService,
MeasurementService,
AlarmService,
OperationBulkService,
OperationService,
ApplicationService,
UserService,
TenantService,
SystemOptionsService,
TenantOptionsService,
TenantSecurityOptionsService,
TenantLoginOptionsService,
AuditService,
InventoryRoleService,
InventoryBinaryService,
DeviceRegistrationService,
DeviceRegistrationBulkService,
UserRoleService,
UserGroupService,
IdentityService,
TrustedCertificateService,
CrlService,
SmartGroupsService,
SmartRulesService,
FeatureService
];
function toProvider(provide) {
let deps = [FetchClient, Realtime];
if (provide === FetchClient) {
deps = [CookieAuth];
}
if (provide === BasicAuth || provide === CookieAuth) {
deps = [];
}
if (provide === Realtime) {
deps = [FetchClient];
}
const depsWithExistingProvider = [
[provide, new Optional(), new SkipSelf()], // let's see if we can find an existing provider (possibly null)
...deps
];
return {
provide,
deps: depsWithExistingProvider,
useFactory: (...args) => {
// if there was an existing provider, it would be first in the list and we can return it
if (args.length && args[0] !== null) {
return args[0];
}
// otherwise we need to drop the first element as it would be null
const providers = args.slice(1);
// the other providers are the dependencies we need. They match the order of the deps array
const existingProviders = deps.map((dep, index) => {
return { provide: dep, useValue: providers[index] };
});
// use this to dependency inject the dependencies and instantiate the class
return runInInjectionContext(Injector.create({
providers: [...existingProviders, { provide, useClass: provide, deps: deps }]
}), () => {
return inject(provide);
});
}
};
}
/**
* Provides all services from `@c8y/client` library.
* @returns An array of providers for all services from `@c8y/client` library.
*/
function provideClientLibServices() {
const providers = defaultServicesFromClientLib
.map(service => toProvider(service))
.concat([
{ provide: ApiService, useClass: ApiService, deps: [FetchClient] },
// ensure that the auth strategy is set before the app is initialized
provideAppInitializer(() => {
const client = inject(FetchClient);
const auth = pickAuthStrategy(BearerAuthFromSessionStorage, BasicAuth, CookieAuth);
client.setAuth(auth);
})
]);
return providers;
}
/**
* @deprecated use provideClientLibServices instead
*/
class DataModule {
static providers() {
return provideClientLibServices();
}
static forRoot() {
return {
ngModule: DataModule,
providers: provideClientLibServices()
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DataModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: DataModule }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DataModule, providers: provideClientLibServices() }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DataModule, decorators: [{
type: NgModule,
args: [{
providers: provideClientLibServices()
}]
}] });
// do not expose as it might confuse people on what to implement
// export * from './http-handler.model';
/**
* Generated bundle index. Do not edit.
*/
export { ApiService, DataModule, HttpHandler, defaultServicesFromClientLib, provideClientLibServices };
//# sourceMappingURL=c8y-ngx-components-api.mjs.map