diff --git a/libs/angular-remote-components/package.json b/libs/angular-remote-components/package.json index 599e295b..bb8e8a3c 100644 --- a/libs/angular-remote-components/package.json +++ b/libs/angular-remote-components/package.json @@ -5,8 +5,10 @@ "@angular/common": "^15.2.7", "@angular/core": "^15.2.7", "@onecx/angular-accelerator": "^4", + "@onecx/integration-interface": "^4", "@ngx-translate/core": "^14.0.0", - "rxjs": "~7.8.0" + "rxjs": "~7.8.0", + "@angular-architects/module-federation": "15.0.0" }, "dependencies": {}, "publishConfig": { diff --git a/libs/angular-remote-components/src/index.ts b/libs/angular-remote-components/src/index.ts index e943e419..d6f8b139 100644 --- a/libs/angular-remote-components/src/index.ts +++ b/libs/angular-remote-components/src/index.ts @@ -4,4 +4,5 @@ export * from './lib/model/injection-tokens' export * from './lib/components/slot/slot.component' export * from './lib/angular-remote-components.module' export * from './lib/services/slot.service' -export * from './lib/utils/provide-translate-service-for-root.utils' \ No newline at end of file +export * from './lib/services/permission.service' +export * from './lib/utils/provide-translate-service-for-root.utils' diff --git a/libs/angular-remote-components/src/lib/angular-remote-components.module.ts b/libs/angular-remote-components/src/lib/angular-remote-components.module.ts index 9df0bb4e..43a2f528 100644 --- a/libs/angular-remote-components/src/lib/angular-remote-components.module.ts +++ b/libs/angular-remote-components/src/lib/angular-remote-components.module.ts @@ -7,6 +7,6 @@ import { AppConfigService } from '@onecx/angular-accelerator' imports: [CommonModule], declarations: [SlotComponent], exports: [SlotComponent], - providers: [AppConfigService] + providers: [AppConfigService], }) export class AngularRemoteComponentsModule {} diff --git a/libs/angular-remote-components/src/lib/services/permission.service.ts b/libs/angular-remote-components/src/lib/services/permission.service.ts new file mode 100644 index 00000000..b052888b --- /dev/null +++ b/libs/angular-remote-components/src/lib/services/permission.service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@angular/core' +import { PermissionsRpcTopic } from '@onecx/integration-interface' +import { filter, firstValueFrom, map } from 'rxjs' + +@Injectable({ providedIn: 'root' }) +export class PermissionService { + private permissionsTopic$ = new PermissionsRpcTopic() + + async getPermissions(appId: string, productName: string): Promise { + const permissions = firstValueFrom( + this.permissionsTopic$.pipe( + filter( + (message) => + message.appId === appId && message.productName === productName && Array.isArray(message.permissions) + ), + map((message) => message.permissions ?? []) + ) + ) + this.permissionsTopic$.publish({ appId: appId, productName: productName }) + return permissions + } +} diff --git a/libs/angular-remote-components/src/lib/services/slot.service.ts b/libs/angular-remote-components/src/lib/services/slot.service.ts index c753556f..05fffde3 100644 --- a/libs/angular-remote-components/src/lib/services/slot.service.ts +++ b/libs/angular-remote-components/src/lib/services/slot.service.ts @@ -1,19 +1,77 @@ -import { InjectionToken, Type } from '@angular/core' -import { Observable } from 'rxjs' +import { loadRemoteModule } from '@angular-architects/module-federation' +import { Injectable, InjectionToken, Type } from '@angular/core' +import { RemoteComponent, RemoteComponentsTopic } from '@onecx/integration-interface' +import { Observable, map, shareReplay } from 'rxjs' +import { PermissionService } from './permission.service' export const SLOT_SERVICE: InjectionToken = new InjectionToken('SLOT_SERVICE') export type RemoteComponentInfo = { appId: string; productName: string; baseUrl: string } export type SlotComponentConfiguration = { - componentType: Promise | undefined> | Type | undefined; - remoteComponent: RemoteComponentInfo; - permissions: Promise | string[]; + componentType: Promise | undefined> | Type | undefined + remoteComponent: RemoteComponentInfo + permissions: Promise | string[] } export interface SlotService { init(): Promise - getComponentsForSlot( - slotName: string - ): Observable + getComponentsForSlot(slotName: string): Observable + isSomeComponentDefinedForSlot(slotName: string): Observable +} + +@Injectable({ providedIn: 'root' }) +export class SlotService implements SlotService { + remoteComponents$ = new RemoteComponentsTopic() + + constructor(private permissionsService: PermissionService) {} + + async init(): Promise { + return Promise.resolve() + } + + getComponentsForSlot(slotName: string): Observable { + return this.remoteComponents$.pipe( + map((remoteComponentsInfo) => + (remoteComponentsInfo.slots?.find((slotMapping) => slotMapping.name === slotName)?.components ?? []) + .map((remoteComponentName) => remoteComponentsInfo.components.find((rc) => rc.name === remoteComponentName)) + .filter((remoteComponent): remoteComponent is RemoteComponent => !!remoteComponent) + .map((remoteComponent) => remoteComponent) + ), + map((infos) => + infos.map((remoteComponent) => ({ + componentType: this.loadComponent(remoteComponent), + remoteComponent, + permissions: this.permissionsService.getPermissions(remoteComponent.appId, remoteComponent.productName), + })) + ), + shareReplay() + ) + } + + isSomeComponentDefinedForSlot(slotName: string): Observable { + return this.remoteComponents$.pipe( + map((remoteComponentsInfo) => remoteComponentsInfo.slots.some((slotMapping) => slotMapping.name === slotName)) + ) + } + + private async loadComponent(component: { + remoteEntryUrl: string + exposedModule: string + }): Promise | undefined> { + try { + const exposedModule = component.exposedModule.startsWith('./') + ? component.exposedModule.slice(2) + : component.exposedModule + const m = await loadRemoteModule({ + type: 'module', + remoteEntry: component.remoteEntryUrl, + exposedModule: './' + exposedModule, + }) + return m[exposedModule] + } catch (e) { + console.log('Failed to load remote module ', component.exposedModule, component.remoteEntryUrl, e) + return undefined + } + } } diff --git a/libs/integration-interface/src/index.ts b/libs/integration-interface/src/index.ts index d531a39b..340c3260 100644 --- a/libs/integration-interface/src/index.ts +++ b/libs/integration-interface/src/index.ts @@ -25,9 +25,14 @@ export * from './lib/topics/message/v1/message.model' export * from './lib/topics/message/v1/message.topic' export * from './lib/topics/remote-components/v1/remote-component.model' +export * from './lib/topics/remote-components/v1/remote-components-info.model' +export * from './lib/topics/remote-components/v1/slot.model' export * from './lib/topics/remote-components/v1/remote-components.topic' export * from './lib/topics/permissions/v1/permissions.topic' +export * from './lib/topics/permissions-rpc/v1/permissions-rpc.topic' +export * from './lib/topics/permissions-rpc/v1/permissions-rpc.model' + export * from './lib/topics/events/v1/events-topic' export * from './lib/topics/events/v1/topic-event-type' diff --git a/libs/integration-interface/src/lib/topics/permissions-rpc/v1/permissions-rpc.model.ts b/libs/integration-interface/src/lib/topics/permissions-rpc/v1/permissions-rpc.model.ts new file mode 100644 index 00000000..4d05ed70 --- /dev/null +++ b/libs/integration-interface/src/lib/topics/permissions-rpc/v1/permissions-rpc.model.ts @@ -0,0 +1,5 @@ +export interface PermissionsRpc { + appId: string + productName: string + permissions?: Array +} diff --git a/libs/integration-interface/src/lib/topics/permissions-rpc/v1/permissions-rpc.topic.ts b/libs/integration-interface/src/lib/topics/permissions-rpc/v1/permissions-rpc.topic.ts new file mode 100644 index 00000000..f1ebc0fc --- /dev/null +++ b/libs/integration-interface/src/lib/topics/permissions-rpc/v1/permissions-rpc.topic.ts @@ -0,0 +1,8 @@ +import { Topic } from '@onecx/accelerator' +import { PermissionsRpc } from './permissions-rpc.model' + +export class PermissionsRpcTopic extends Topic { + constructor() { + super('permissionsRpc', 1) + } +} diff --git a/libs/integration-interface/src/lib/topics/permissions/v1/permissions.topic.ts b/libs/integration-interface/src/lib/topics/permissions/v1/permissions.topic.ts index de03d49b..0d1a7b55 100644 --- a/libs/integration-interface/src/lib/topics/permissions/v1/permissions.topic.ts +++ b/libs/integration-interface/src/lib/topics/permissions/v1/permissions.topic.ts @@ -1,7 +1,7 @@ -import { SyncableTopic } from "@onecx/accelerator"; +import { SyncableTopic } from '@onecx/accelerator' export class PermissionsTopic extends SyncableTopic { - constructor() { - super('permissions', 1) - } - } \ No newline at end of file + constructor() { + super('permissions', 1) + } +} diff --git a/libs/integration-interface/src/lib/topics/remote-components/v1/remote-component.model.ts b/libs/integration-interface/src/lib/topics/remote-components/v1/remote-component.model.ts index 91d0c84c..80ffe205 100644 --- a/libs/integration-interface/src/lib/topics/remote-components/v1/remote-component.model.ts +++ b/libs/integration-interface/src/lib/topics/remote-components/v1/remote-component.model.ts @@ -1,8 +1,8 @@ -export interface RemoteComponent { - name: string; - baseUrl: string; - remoteEntryUrl: string; - appId: string; - productName: string; - exposedModule: string; -} \ No newline at end of file +export type RemoteComponent = { + name: string + baseUrl: string + remoteEntryUrl: string + appId: string + productName: string + exposedModule: string +} diff --git a/libs/integration-interface/src/lib/topics/remote-components/v1/remote-components-info.model.ts b/libs/integration-interface/src/lib/topics/remote-components/v1/remote-components-info.model.ts new file mode 100644 index 00000000..a067c03c --- /dev/null +++ b/libs/integration-interface/src/lib/topics/remote-components/v1/remote-components-info.model.ts @@ -0,0 +1,4 @@ +import { RemoteComponent } from './remote-component.model' +import { Slot } from './slot.model' + +export type RemoteComponentsInfo = { components: RemoteComponent[]; slots: Slot[] } diff --git a/libs/integration-interface/src/lib/topics/remote-components/v1/remote-components.topic.ts b/libs/integration-interface/src/lib/topics/remote-components/v1/remote-components.topic.ts index 1fe996db..c6eec09e 100644 --- a/libs/integration-interface/src/lib/topics/remote-components/v1/remote-components.topic.ts +++ b/libs/integration-interface/src/lib/topics/remote-components/v1/remote-components.topic.ts @@ -1,8 +1,8 @@ import { Topic } from '@onecx/accelerator' -import { RemoteComponent } from './remote-component.model' +import { RemoteComponentsInfo } from './remote-components-info.model' -export class RemoteComponentsTopic extends Topic { +export class RemoteComponentsTopic extends Topic { constructor() { - super('remoteComponents', 1) + super('remoteComponentsInfo', 1) } } diff --git a/libs/integration-interface/src/lib/topics/remote-components/v1/slot.model.ts b/libs/integration-interface/src/lib/topics/remote-components/v1/slot.model.ts new file mode 100644 index 00000000..3ce1df35 --- /dev/null +++ b/libs/integration-interface/src/lib/topics/remote-components/v1/slot.model.ts @@ -0,0 +1,4 @@ +export type Slot = { + name: string + components: Array +}