/* * Copyright (C) 2021-2022 Kristóf Marussy * * This file is part of Sophie. * * Sophie is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * SPDX-License-Identifier: AGPL-3.0-only */ import type { IJsonPatch } from 'mobx-state-tree'; import { Action, action, sharedStore, SharedStoreListener, SophieRenderer, } from '@sophie/shared'; import { RendererToMainIpcService } from '../services/RendererToMainIpcService'; class SophieRendererImpl implements SophieRenderer { readonly #ipcService: RendererToMainIpcService; #onSharedStoreChangeCalled = false; #listener: SharedStoreListener | null = null; constructor(ipcService: RendererToMainIpcService) { this.#ipcService = ipcService; ipcService.onSharedStorePatch((patch) => { try { // `mobx-state-tree` will validate the patch, so we can safely cast here. this.#listener?.onPatch(patch as IJsonPatch); } catch (err) { console.log('Shared store listener onPatch failed', err); } }); } async #setSharedStoreChangeListener(listener: SharedStoreListener): Promise { let snapshot: unknown; try { snapshot = await this.#ipcService.getSharedStoreSnapshot(); } catch (err) { console.error('Failed to get initial shared store snapshot', err); return; } if (sharedStore.is(snapshot)) { try { listener.onSnapshot(snapshot); } catch (err) { console.error('Shared store listener onSnapshot failed', err); return; } this.#listener = listener; return; } console.error('Got invalid initial shared store snapshot', snapshot); } onSharedStoreChange(listener: SharedStoreListener): void { if (this.#onSharedStoreChangeCalled) { throw new Error('Shared store change listener was already set'); } this.#setSharedStoreChangeListener(listener).catch((err) => { console.log('Failed to set shared store change listener', err); }); } dispatchAction(actionToDispatch: Action): void { // Let the full zod parse error bubble up to the main world, // since all data it may contain was provided from the main world. const parsedAction = action.parse(actionToDispatch); try { this.#ipcService.dispatchAction(parsedAction); } catch (err) { // Do not leak IPC failure details into the main world. const message = 'Failed to dispatch action'; console.error(message, actionToDispatch, err); throw new Error(message); } } } export function createSophieRenderer(ipcService: RendererToMainIpcService): SophieRenderer { const impl = new SophieRendererImpl(ipcService); return { onSharedStoreChange: impl.onSharedStoreChange.bind(impl), dispatchAction: impl.dispatchAction.bind(impl), }; }