import {Application, Component, Configurable, Exception, Inject} from '@lakutata/core' import {ClientComponent} from './ClientComponent' import {Channel} from '../../lib/Channel' import {Logger} from '@lakutata/core/build/plugins/Logger' import {TChannelEvent} from '../../types/TChannelEvent' import HyperID from 'hyperid' import {Service} from '../../Service' import { SERVICE_CHANNEL_PRE_PUBLISH_EVENT, SERVICE_CHANNEL_PROXY_PUBLISH_EVENT, SERVICE_CHANNEL_PUBLISH_EVENT, SERVICE_CHANNEL_SUBSCRIBE_EVENT } from '../../constants/SocketConstant' import {ON_ANY_EVENT_RESERVED_SYMBOL} from '../../constants/ModuleConstant' export class ChannelComponent extends Component { @Inject(Application) public readonly app: Application @Inject('client') protected client: ClientComponent @Configurable() public readonly service: Service @Configurable() protected readonly logger: () => Logger | undefined protected readonly hyperIdInstance: HyperID.Instance = HyperID({fixedLength: true, urlSafe: true}) protected readonly channelMap: Map = new Map() public get channelEvents(): { [key: string]: string[] } { const channelEvents: { [key: string]: string[] } = {} this.channelMap.forEach((channel: Channel, name: string) => { if (!channelEvents[name]) { channelEvents[name] = [] } channel.eventEmitter.eventNames().forEach(eventName => { if (typeof eventName === 'string') channelEvents[name].push(eventName) }) }) return channelEvents } /** * 组件初始化函数 * @protected */ protected async initialize(): Promise { this.client.onSocketMessage(SERVICE_CHANNEL_SUBSCRIBE_EVENT, (eventStringPacket: string) => this.serviceChannelSubscribeEventHandler(eventStringPacket)) this.client.onHttpRequest(SERVICE_CHANNEL_SUBSCRIBE_EVENT, (eventStringPacket: string) => this.serviceChannelSubscribeEventHandler(eventStringPacket)) } /** * 服务订阅事件处理方法 * @param eventStringPacket * @protected */ protected serviceChannelSubscribeEventHandler(eventStringPacket: string): void { const eventPacket: TChannelEvent = this.app.JSON.parse(eventStringPacket) this.channelMap.get(eventPacket.channel)?.eventEmitter.emit(ON_ANY_EVENT_RESERVED_SYMBOL, eventPacket.event, ...eventPacket.args) const isSuccess: boolean = !!(this.channelMap.get(eventPacket.channel)?.eventEmitter.emit(eventPacket.event, ...eventPacket.args)) if (isSuccess) { this.logger()?.info( eventPacket.id, `[RECEIVED-SUCCESS]`, 'from', `[${eventPacket.source}]`, eventPacket.channel, eventPacket.event, `(${eventPacket.mode})` ) } else { this.logger()?.warning( eventPacket.id, `[RECEIVED-FAILED]`, 'from', `[${eventPacket.source}]`, eventPacket.channel, eventPacket.event, `(${eventPacket.mode})` ) } } /** * 创建新通讯通道 * @param name * @protected */ protected createChannel(name: string): Channel { return new Channel(name, this) } /** * 构建通道事件数据包 * @param channel * @param appId * @param eventName * @param mode * @param args * @protected */ protected buildEventPacket(channel: string, appId: string | undefined, eventName: string, mode: 'all' | 'one', ...args: any[]): string { const packet: TChannelEvent = { id: this.hyperIdInstance(), channel: channel, event: eventName, mode: mode, source: this.app.getID(), app: appId, args: args ? args : [] } return this.app.JSON.stringify(packet) } /** * 构建通道事件数据包(缩略信息包),用于事件的预发布 * @param channel * @param appId * @param eventName * @param mode * @param accessibleURLs * @protected */ protected buildEventThumbPacket(channel: string, appId: string | undefined, eventName: string, mode: 'all' | 'one', accessibleURLs: Set): string { const packet: Partial & { accessibleURLs: string[] } = { id: this.hyperIdInstance(), channel: channel, event: eventName, mode: mode, source: this.app.getID(), app: appId, accessibleURLs: [...accessibleURLs] } return this.app.JSON.stringify(packet) } /** * 构建服务中心代理事件发送数据包 * @param channel * @param appId * @param eventName * @param mode * @param targetAppURLMap * @param args * @protected */ protected buildProxyEventPacket(channel: string, appId: string | undefined, eventName: string, mode: 'all' | 'one', targetAppURLMap: Map, ...args: any[]): string { const packet: { data: string; targets: [string, string[]][] } = { data: this.buildEventPacket(channel, appId, eventName, mode, ...args), targets: [...targetAppURLMap] } return this.app.JSON.stringify(packet) } /** * 发布渠道事件 * @param channel * @param appId * @param eventName * @param mode * @param args */ public publishEvent(channel: string, appId: string | undefined, eventName: string, mode: 'all' | 'one', ...args: any[]): boolean { const eventStringPacket: string = this.buildEventPacket(channel, appId, eventName, mode, ...args) if (!this.client.accessibleURLs.size) return this.client.socketSend(SERVICE_CHANNEL_PUBLISH_EVENT, eventStringPacket) process.nextTick(async () => { const stringifyTargetAppURLMap: string = await this.client.socketRequest(SERVICE_CHANNEL_PRE_PUBLISH_EVENT, this.buildEventThumbPacket(channel, appId, eventName, mode, this.client.accessibleURLs)) const targetAppURLMap: Map = new Map(JSON.parse(stringifyTargetAppURLMap)) const unaccessibleTargetAppURLMap: Map = new Map() if (!targetAppURLMap.size) return//在没有任何消息接收者时直接中断后续步骤 const directEmitEventPromises: Promise[] = [] targetAppURLMap.forEach((appURLs: string[], appId: string) => { const unaccessibleURLs: string[] = [] appURLs.forEach((appURL: string) => { if (this.client.accessibleURLs.has(appURL)) { directEmitEventPromises.push(new Promise(resolve => this.client.httpRequest(appURL, SERVICE_CHANNEL_SUBSCRIBE_EVENT, eventStringPacket) .then(() => resolve()) .catch((exception: Exception) => { if (exception.errno === 'E_HTTP_REQUEST_EVENT_NOT_FOUND') { if (!unaccessibleTargetAppURLMap.has(appId)) unaccessibleTargetAppURLMap.set(appId, []) unaccessibleTargetAppURLMap.get(appId)?.push(appURL) } return resolve() }))) } else { unaccessibleURLs.push(appURL) } }) if (unaccessibleURLs.length) { if (!unaccessibleTargetAppURLMap.has(appId)) unaccessibleTargetAppURLMap.set(appId, []) unaccessibleURLs.forEach(unaccessibleURL => unaccessibleTargetAppURLMap.get(appId)?.push(unaccessibleURL)) } }) await Promise.all(directEmitEventPromises) if (!unaccessibleTargetAppURLMap.size) return //若有无法访问的应用程序URL,则将URL集合以及事件内容发送至服务中心,由服务中心负责事件发送 this.client.socketSend(SERVICE_CHANNEL_PROXY_PUBLISH_EVENT, this.buildProxyEventPacket(channel, appId, eventName, mode, unaccessibleTargetAppURLMap, ...args)) }) return true } /** * 获取通讯通道 * @param name */ public get(name: string): Channel { if (!this.channelMap.has(name)) { this.channelMap.set(name, this.createChannel(name)) } return this.channelMap.get(name)! } /** * 获取通讯通道列表 */ public list(): string[] { const channelNames: string[] = [] this.channelMap.forEach((channel: Channel, name: string) => channelNames.push(name)) return channelNames } }