import {LogCategory, logError, logInfo, logWarn} from "tc-shared/log"; import {getIpcInstance, IPCChannel} from "tc-shared/ipc/BrowserIPC"; import {Registry} from "tc-events"; import {ModalOptions} from "tc-shared/ui/react-elements/modal/Definitions"; import {guid} from "tc-shared/crypto/uid"; import {ModalInstanceController, ModalInstanceEvents, ModalState} from "tc-shared/ui/react-elements/modal/Definitions"; import {getWindowManager} from "tc-shared/ui/windows/WindowManager"; import {assertMainApplication} from "tc-shared/ui/utils"; import { ModalIPCController2Renderer, ModalIPCRenderer2ControllerMessages } from "tc-shared/ui/react-elements/modal/external/Definitions"; assertMainApplication(); export class ExternalModalController implements ModalInstanceController { private readonly modalType: string; private readonly modalOptions: ModalOptions; private readonly constructorArguments: any[]; private readonly mainModalId: string; private readonly ipcMessageHandler: { [key: string]: (sourcePeerId: string, message: any) => void }; private ipcRemotePeerId: string; private ipcChannel: IPCChannel; private readonly modalEvents: Registry; private modalInitializeCallback: () => void; private windowId: string | undefined; private windowListener: (() => void)[]; private windowMutatePromise: Promise; constructor(modalType: string, constructorArguments: any[], modalOptions: ModalOptions) { this.modalType = modalType; this.modalOptions = modalOptions; this.constructorArguments = constructorArguments; this.mainModalId = guid(); this.modalEvents = new Registry(); this.ipcMessageHandler = {}; this.ipcChannel = getIpcInstance().createChannel(guid()); this.ipcChannel.messageHandler = (sourcePeerId, broadcast, message) => { if(sourcePeerId !== this.ipcRemotePeerId && message.type !== "hello-renderer") { return; } if(typeof this.ipcMessageHandler[message.type] !== "function") { logWarn(LogCategory.IPC, tr("Received remote modal message but we don't know how to handle the message (%s)."), message.type); return; } this.ipcMessageHandler[message.type](sourcePeerId, message.data); }; this.registerIpcMessageHandler("hello-renderer", (sourcePeerId, message) => { if(this.ipcRemotePeerId) { logInfo(LogCategory.IPC, tr("Modal %s got reloaded (Version: %s). Using new peer address %s instead of %s and initializing it."), this.modalType, message.version, this.ipcRemotePeerId, sourcePeerId); this.sendIpcMessage("invalidate-modal-instance", { }); } else { logInfo(LogCategory.IPC, tr("Modal %s has called back (Version: %s). Initializing it."), this.modalType, message.version); } this.ipcRemotePeerId = sourcePeerId; if(message.version !== __build.version) { this.sendIpcMessage("hello-controller", { accepted: false, message: tr("version miss match") }); return; } if(this.modalInitializeCallback) { this.modalInitializeCallback(); } this.sendIpcMessage("hello-controller", { accepted: true, modalId: this.mainModalId, modalType: this.modalType, constructorArguments: this.constructorArguments }); }); this.registerIpcMessageHandler("invoke-modal-action", (sourcePeerId, message) => { if(message.modalId !== this.mainModalId) { return; } switch (message.action) { case "minimize": this.modalEvents.fire("action_minimize"); break; case "close": this.modalEvents.fire("action_close"); break; } }); } destroy() { this.hide().then(undefined); this.modalEvents.destroy(); } getEvents(): Registry { return this.modalEvents; } getState(): ModalState { return typeof this.windowId === "string" ? ModalState.SHOWN : ModalState.DESTROYED; } async show(): Promise { await this.mutateWindow(async () => { const windowManager = getWindowManager(); if(typeof this.windowId === "string") { if(windowManager.isActionSupported(this.windowId, "focus")) { await windowManager.executeAction(this.windowId, "focus"); } return; } const result = await windowManager.createWindow({ uniqueId: this.modalOptions.uniqueId || this.modalType, loaderTarget: "modal-external", windowName: "modal " + this.modalType, defaultSize: this.modalOptions.defaultSize, appParameters: { "modal-channel": this.ipcChannel.channelId, } }); if(result.status === "error-user-rejected") { throw tr("user rejected"); } else if(result.status === "error-unknown") { throw result.message; } this.windowListener = []; this.windowListener.push(windowManager.getEvents().on("notify_window_destroyed", event => { if(event.windowId !== this.windowId) { return; } this.handleWindowDestroyed(); })); this.windowId = result.windowId; try { await new Promise((resolve, reject) => { this.modalInitializeCallback = resolve; setTimeout(reject, 15000); }); } catch (_) { logError(LogCategory.IPC, tr("Opened modal failed to call back within 15 seconds.")); getWindowManager().destroyWindow(this.windowId); } finally { this.modalInitializeCallback = undefined; } }); } async hide(): Promise { await this.mutateWindow(async () => { if(typeof this.windowId !== "string") { return; } getWindowManager().destroyWindow(this.windowId); }); } async minimize(): Promise { await this.mutateWindow(async () => { if (typeof this.windowId !== "string") { return; } await getWindowManager().executeAction(this.windowId, "minimize"); }); } async maximize(): Promise { await this.mutateWindow(async () => { if (typeof this.windowId !== "string") { return; } await getWindowManager().executeAction(this.windowId, "maximize"); }); } private async mutateWindow(callback: () => Promise) : Promise { while(this.windowMutatePromise) { await this.windowMutatePromise; } return await new Promise((resolveOuter, rejectOuter) => { const promise = callback(); this.windowMutatePromise = new Promise(resolve => { promise.then(result => { this.windowMutatePromise = undefined; resolve(); resolveOuter(result); }).catch(error => { this.windowMutatePromise = undefined; resolve(); rejectOuter(error); }); }); }); } private handleWindowDestroyed() { this.windowId = undefined; this.windowListener.forEach(callback => callback()); this.modalEvents.fire("notify_destroy"); } private registerIpcMessageHandler( type: T, handler: (sourcePeerId: string, message: ModalIPCRenderer2ControllerMessages[T]) => void ) { this.ipcMessageHandler[type] = handler; } private sendIpcMessage(type: T, message: ModalIPCController2Renderer[T]) { if(!this.ipcRemotePeerId) { logWarn(LogCategory.IPC, tr("Tried to send a modal message but we don't know the modals peer address.")); return; } this.ipcChannel.sendMessage(type, message, this.ipcRemotePeerId); } }