import { BrowserWindow, Extension, session } from 'electron'; import * as fs from 'fs'; import * as path from 'path'; import { createLogger } from '../../logger'; const logger = createLogger('main:yomitan-extension-loader'); export interface YomitanExtensionLoaderDeps { userDataPath: string; getYomitanParserWindow: () => BrowserWindow | null; setYomitanParserWindow: (window: BrowserWindow | null) => void; setYomitanParserReadyPromise: (promise: Promise | null) => void; setYomitanParserInitPromise: (promise: Promise | null) => void; setYomitanExtension: (extension: Extension | null) => void; } function ensureExtensionCopy(sourceDir: string, userDataPath: string): string { if (process.platform === 'win32') { return sourceDir; } const extensionsRoot = path.join(userDataPath, 'extensions'); const targetDir = path.join(extensionsRoot, 'yomitan'); const sourceManifest = path.join(sourceDir, 'manifest.json'); const targetManifest = path.join(targetDir, 'manifest.json'); let shouldCopy = !fs.existsSync(targetDir); if (!shouldCopy && fs.existsSync(sourceManifest) && fs.existsSync(targetManifest)) { try { const sourceVersion = ( JSON.parse(fs.readFileSync(sourceManifest, 'utf-8')) as { version: string; } ).version; const targetVersion = ( JSON.parse(fs.readFileSync(targetManifest, 'utf-8')) as { version: string; } ).version; shouldCopy = sourceVersion !== targetVersion; } catch { shouldCopy = true; } } if (shouldCopy) { fs.mkdirSync(extensionsRoot, { recursive: true }); fs.rmSync(targetDir, { recursive: true, force: true }); fs.cpSync(sourceDir, targetDir, { recursive: true }); logger.info(`Copied yomitan extension to ${targetDir}`); } return targetDir; } export async function loadYomitanExtension( deps: YomitanExtensionLoaderDeps, ): Promise { const searchPaths = [ path.join(__dirname, '..', '..', 'vendor', 'yomitan'), path.join(__dirname, '..', '..', '..', 'vendor', 'yomitan'), path.join(process.resourcesPath, 'yomitan'), '/usr/share/SubMiner/yomitan', path.join(deps.userDataPath, 'yomitan'), ]; let extPath: string | null = null; for (const p of searchPaths) { if (fs.existsSync(p)) { extPath = p; break; } } if (!extPath) { logger.error('Yomitan extension not found in any search path'); logger.error('Install Yomitan to one of:', searchPaths); return null; } extPath = ensureExtensionCopy(extPath, deps.userDataPath); const parserWindow = deps.getYomitanParserWindow(); if (parserWindow && !parserWindow.isDestroyed()) { parserWindow.destroy(); } deps.setYomitanParserWindow(null); deps.setYomitanParserReadyPromise(null); deps.setYomitanParserInitPromise(null); try { const extensions = session.defaultSession.extensions; const extension = extensions ? await extensions.loadExtension(extPath, { allowFileAccess: true, }) : await session.defaultSession.loadExtension(extPath, { allowFileAccess: true, }); deps.setYomitanExtension(extension); return extension; } catch (err) { logger.error('Failed to load Yomitan extension:', (err as Error).message); logger.error('Full error:', err); deps.setYomitanExtension(null); return null; } }