/* * 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 { readFile } from 'node:fs/promises'; import { ServiceToMainIpcMessage } from '@sophie/service-shared'; import { ipcMain, WebSource } from 'electron'; import type MainStore from '../../../stores/MainStore'; import type Profile from '../../../stores/Profile'; import type Service from '../../../stores/Service'; import { getLogger } from '../../../utils/log'; import type Resources from '../../resources/Resources'; import type UserAgents from '../UserAgents'; import type { MainWindow, Partition, ServiceView, ViewFactory } from '../types'; import ElectronMainWindow from './ElectronMainWindow'; import ElectronPartition from './ElectronPartition'; import ElectronServiceView from './ElectronServiceView'; const log = getLogger('ElectronViewFactory'); export default class ElectronViewFactory implements ViewFactory { private readonly webContentsIdToServiceView = new Map< number, ElectronServiceView >(); private serviceInjectSource: WebSource | undefined; constructor( readonly userAgents: UserAgents, readonly resources: Resources, readonly devMode: boolean, ) { ipcMain.handle(ServiceToMainIpcMessage.ApiExposedInMainWorld, (event) => { if (!this.webContentsIdToServiceView.has(event.sender.id)) { log.error( 'Unexpected', ServiceToMainIpcMessage.ApiExposedInMainWorld, 'IPC message from webContents', event.sender.id, ); throw new Error('Invalid IPC call'); } if (this.serviceInjectSource === undefined) { log.error('Service inject source was not loaded'); } return this.serviceInjectSource; }); } async createMainWindow(store: MainStore): Promise { const mainWindow = new ElectronMainWindow(store, this); await mainWindow.loadInterface(); return mainWindow; } createPartition(profile: Profile): Partition { return new ElectronPartition(profile, this); } createServiceView(service: Service, partition: Partition): ServiceView { if (partition instanceof ElectronPartition) { const serviceView = new ElectronServiceView( service, this.resources, partition, this, ); this.webContentsIdToServiceView.set( serviceView.webContentsId, serviceView, ); return serviceView; } throw new TypeError('Unexpected ProfileSession is not a WrappedSession'); } async loadServiceInject(): Promise { const injectPackage = 'service-inject'; const injectFile = 'index.js'; const injectPath = this.resources.getPath(injectPackage, injectFile); this.serviceInjectSource = { code: await readFile(injectPath, 'utf8'), url: this.resources.getFileURL(injectPackage, injectFile), }; } dispose(): void { if (this.webContentsIdToServiceView.size > 0) { throw new Error( 'Must dispose all ServiceView instances before disposing ViewFactory', ); } ipcMain.removeHandler(ServiceToMainIpcMessage.ApiExposedInMainWorld); } unregisterServiceView(id: number): void { this.webContentsIdToServiceView.delete(id); } }