core/src/channel/index.ts

176 lines
4.5 KiB
TypeScript

import { IPlugin } from '../plugin';
import { IMessage, MessageResolver, Protocol } from '../types';
import { ScopedEventEmitter } from '../util/events';
export interface IChannel {
name: string;
plugins: string[];
enabled: boolean;
}
/**
* This class is used to direct messages and events from one plugin to many others
* using a pre-set list of plugins that are allowed to talk to one another.
*
* Generally when creating a channel, the first plugin should be the source of messages
* or events, such as a protocol or other service, and the rest of the plugins in the
* list are the handlers.
*/
export class ChannelManager {
protected channels: IChannel[] = [];
constructor(protected stream: ScopedEventEmitter) {}
/**
* Ensure that the message or event source is a plugin
* @param source Event source
* @returns Plugin or null
*/
public static determinePlugin(source: any): IPlugin | null {
if (source != null) {
if (source.manifest) {
return source;
}
if (source.plugin && source.plugin.manifest) {
return source.plugin;
}
}
return null;
}
/**
* Initialize the event handlers for channels
* @param configured Initial configuration of channels
*/
public initialize(configured: IChannel[]): void {
this.addPreconfiguredChannels(configured);
for (const event of ['message', 'event', 'special']) {
this.stream.on('channel', event, (data: IMessage) => {
let msr;
if (event === 'message') {
msr = new MessageResolver(data);
}
const plugin = ChannelManager.determinePlugin(data.source);
if (!plugin) {
return;
}
const source = plugin.manifest.name;
const emitTo = this.getChannelsByPluginName(source, data.source);
for (const chan of emitTo) {
if (chan.plugins.length < 2) {
continue;
}
for (const pl of chan.plugins) {
if (
pl !== source &&
!(pl.indexOf('/') !== -1 && pl.split('/')[0] === source)
) {
this.stream.emitTo(pl, event, data, chan, msr);
}
}
}
});
}
}
/**
* Get all the channels a plugin is in
* @param plugin Plugin name
* @param source Source protocol of the event
* @returns List of channels to send to
*/
protected getChannelsByPluginName(
plugin: string,
source: Protocol
): IChannel[] {
const list = [];
for (const chan of this.channels) {
if (chan.enabled === false) {
continue;
}
for (const pl of chan.plugins) {
if (pl.indexOf('/') !== -1) {
const split = pl.split('/');
if (split[0] === plugin && split[1] === source.name) {
list.push(chan);
}
} else if (pl === plugin) {
list.push(chan);
}
}
}
return list;
}
/**
* Validate a preconfigured channel list and add them to the list
* @param channels Preconfigured channel list
*/
protected addPreconfiguredChannels(channels: IChannel[]): void {
for (const chan of channels) {
if (!chan.name) {
throw new Error('Channel name is mandatory.');
}
if (!chan.plugins) {
throw new Error('Channel plugins list is mandatory.');
}
this.channels.push(chan);
}
}
/**
* Get a channel by name
* @param name Channel name
* @returns Channel or undefined
*/
public getChannelByName(name: string): IChannel | undefined {
return this.channels.find((c) => c.name === name);
}
/**
* Add a new channel to the channels list
* @param chan Channel configuration
* @returns Channel
*/
public addChannel(chan: IChannel): IChannel {
const exists = this.getChannelByName(chan.name);
if (exists) {
throw new Error('Channel by that name already exists!');
}
this.channels.push(chan);
return chan;
}
/**
* Remove a channel by name or the channel itself
* @param chan Name of channel or channel
*/
public removeChannel(chan: string | IChannel): void {
if (typeof chan === 'string') {
const getchan = this.getChannelByName(chan);
if (!getchan) {
throw new Error('Channel by that name doesn\'t exists!');
}
chan = getchan;
}
this.channels.splice(this.channels.indexOf(chan), 1);
}
/**
* Get all channels
* @returns All channels
*/
public getAll(): IChannel[] {
return this.channels;
}
}