/* * Copyright (C) 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 { SharedStoreSnapshotOut } from '@sophie/shared'; import { addMiddleware, getSnapshot, IJsonPatch, IMiddlewareEvent, onPatch, } from 'mobx-state-tree'; import type MainStore from '../../stores/MainStore'; import Disposer from '../../utils/Disposer'; import { getLogger } from '../../utils/log'; const log = getLogger('RendererBridge'); export type PatchListener = (patch: IJsonPatch[]) => void; export default class RendererBridge { snapshot: SharedStoreSnapshotOut; private readonly disposeOnPatch: Disposer; private readonly disposeMiddleware: Disposer; constructor(store: MainStore, listener: PatchListener) { this.snapshot = getSnapshot(store.shared); // The call for the currently pending action, if any. let topLevelCall: IMiddlewareEvent | undefined; // An array of accumulated patches if we're in an action, `undefined` otherwise. let patches: IJsonPatch[] | undefined; this.disposeOnPatch = onPatch(store.shared, (patch) => { if (patches === undefined) { // Update unprotected stores (outside an action) right away. listener([patch]); } else { patches.push(patch); } }); this.disposeMiddleware = addMiddleware(store, (call, next, abort) => { if (call.parentActionEvent !== undefined) { // We're already in an action, there's no need to enter one. next(call); return; } if (patches !== undefined) { log.error( 'Unexpected call', call, 'during dispatching another call', topLevelCall, 'with accumulated patches', patches, ); abort(undefined); return; } // Make shure that the saved snapshot is consistent with the patches we're going to send. this.snapshot = getSnapshot(store.shared); topLevelCall = call; patches = []; try { next(call); } finally { try { if (patches.length > 0) { listener(patches); } } finally { topLevelCall = undefined; patches = undefined; } } }); } dispose(): void { this.disposeMiddleware(); this.disposeOnPatch(); } }