import { ConstructorType } from '@dooboostore/core'; import {DomRenderFinalProxy} from '../types/Types'; import {DomRenderConfig} from '../configs/DomRenderConfig'; enum CallBackType { FILTER, MAP, SUBSCRIBE } export type ChannelData = { channel: Channel; data?: (any | undefined); } export type ChannelMetaData = { channel?: Channel; action?: string; } export class ChannelSubscription { constructor(public channel: Channel, public subscriber: ChannelSubscriber) { } unsubscribe() { this.subscriber.unsubscribe(); } } export class ChannelSubscriber { public parentSubscriber?: ChannelSubscriber; public callbacks: ({ type: CallBackType, callback: (data: any, metaData: ChannelMetaData) => any | undefined })[] = []; constructor(public channel: Channel) { } exeCallback(data: any, metaData: ChannelMetaData) { for (const callback of this.callbacks) { if (callback.type === CallBackType.FILTER && !callback.callback(data, metaData)) { break; } else if (callback.type === CallBackType.MAP) { data = callback.callback(data, metaData); } else if (callback.type === CallBackType.SUBSCRIBE) { data = callback.callback(data, metaData); break; } } return data; } // chaining point filter(callback: (data: D, meta: ChannelMetaData) => boolean): ChannelSubscriber { this.callbacks.push({type: CallBackType.FILTER, callback}); return this; } map(callback: (data: D, meta: ChannelMetaData) => R): ChannelSubscriber { this.callbacks.push({type: CallBackType.MAP, callback}); return this as unknown as ChannelSubscriber; } subscribe(callback: (data: SD, metaData: ChannelMetaData) => any | undefined) { this.callbacks.push({type: CallBackType.SUBSCRIBE, callback}); this.channel.subscribers.add(this); return new ChannelSubscription(this.channel, this); } unsubscribe() { this.channel.subscribers.delete(this); } deleteSubscriber() { this.unsubscribe(); this.channel.subscribers.delete(this); } } export class Channel { public subscribers = new Set(); constructor(private messenger: Messenger, public obj: object, public key: string) { } publish(key: string | object | ConstructorType, data: any, action?: string) { const rtns: ChannelData[] = []; this.messenger.getChannels(key)?.forEach(it => { try { it.subscribers.forEach(its => { const rdata = its.exeCallback(data, {channel: this, action}); rtns.push({channel: it, data: rdata}); }); } catch (e) { console.error(e); } }); return rtns; } allPublish(data: any, action: string) { const rtns: (any | undefined)[][] = []; this.messenger.getAllChannelKeys().forEach(it => { rtns.push(this.publish(it, data)); }); return rtns.flat(); } // string point filter(filterF: (data: D, meta: ChannelMetaData) => boolean) { const subscriber = new ChannelSubscriber(this); subscriber.filter(filterF); return subscriber; } map(filterF: (data: D, meta: ChannelMetaData) => R): ChannelSubscriber { const subscriber = new ChannelSubscriber(this); subscriber.map(filterF); return subscriber; } subscribe(subscribeCallback: (data: D, meta: ChannelMetaData) => any | void | undefined) { const subscriber = new ChannelSubscriber(this); return subscriber.subscribe(subscribeCallback); }; deleteChannel() { this.messenger.deleteChannel(this); }; } type MessengerEventDetail = { key: string | object | ConstructorType; data?: any; action?: string; result?: (c: ChannelData[]) => void; } type MessengerSubscribeEventDetail = { obj: any; key?: string | object | ConstructorType; init: (channel: Channel, subscription: ChannelSubscription) => void; subscribe: (data: any, meta: ChannelMetaData) => void; } export abstract class Messenger { private channels = new Set(); static readonly EVENT_PUBLISH_KEY = 'domRenderMessenger_publish'; static readonly EVENT_SUBSCRIBE_KEY = 'domRenderMessenger_subscribe'; constructor(private config: DomRenderConfig) { this.config.window.addEventListener(Messenger.EVENT_PUBLISH_KEY, (e: Event) => { const detail = ((e as CustomEvent).detail as MessengerEventDetail); // console.log('--->', detail) const rtns: ChannelData[] = []; this.getChannels(detail.key)?.forEach(it => { try { it.subscribers.forEach(its => { const rdata = its.exeCallback(detail.data, {action: detail.action}); rtns.push({channel: it, data: rdata}); }); } catch (e) { console.error(e); } }); detail.result?.(rtns); }); this.config.window.addEventListener(Messenger.EVENT_SUBSCRIBE_KEY, (e: Event) => { const detail = ((e as CustomEvent).detail as MessengerSubscribeEventDetail); // console.log('--->', detail) const channel = this.createChannel(detail.obj, detail.key); detail.init(channel, channel.subscribe(detail.subscribe)); }); } public static publish(window: Window, detail: MessengerEventDetail) { window.dispatchEvent(new CustomEvent(Messenger.EVENT_PUBLISH_KEY, {detail})); } public static subscribe(window: Window, detail: MessengerSubscribeEventDetail) { window.dispatchEvent(new CustomEvent(Messenger.EVENT_SUBSCRIBE_KEY, {detail})); } createChannel(obj: any, key = obj.constructor.name): Channel { const channel = DomRenderFinalProxy.final(new Channel(this, obj, key)) as Channel; this.channels.add(channel); // this.channels.get(key) ? this.channels.get(key)!.push(channel) : this.channels.set(key, [channel]); return channel; } deleteChannel(channel: Channel) { this.channels.delete(channel); } deleteChannelFromObj(obj: any) { if (obj) { this.channels.forEach(it => { if (it.obj === obj) { // console.log('dddddddddddd', obj) this.deleteChannel(it); } }); } } addChannel(channel: Channel) { this.channels.add(channel); } getChannels(key: string | object | ConstructorType): Channel[] { if (typeof key === 'object') { key = key.constructor.name; } else if (typeof key === 'function') { key = key.name; } return Array.from(this.channels.values()).filter(it => it.key === key); } getAllChannels(): Channel[] { return Array.from(this.channels.values()); } getAllChannelKeys(): string[] { return Array.from(this.channels.values()).map(it => it.key); } }