diff --git a/src/errors/ErrorHandler.ts b/src/errors/ErrorHandler.ts index fff624d..bf2ef8d 100644 --- a/src/errors/ErrorHandler.ts +++ b/src/errors/ErrorHandler.ts @@ -106,7 +106,7 @@ export class ErrorHandler implements IDisposable { // i18n service might not be available yet during initialization try { this.i18n = getI18n(); - } catch (_error) { + } catch { // i18n service will be available later } @@ -130,22 +130,21 @@ export class ErrorHandler implements IDisposable { // Unhandled promise rejections this.unhandledRejectionHandler = (event: PromiseRejectionEvent) => { // Filter out errors that are not from our plugin - const error = event.reason; - const errorString = error?.toString?.() || String(error); + const reasonSummary = this.describeReason(event.reason); // Ignore errors from other plugins/sources - if (errorString.includes('inputEl') || - errorString.includes('Cannot read properties of undefined') || - !errorString.includes('voice-input')) { + if (reasonSummary.includes('inputEl') || + reasonSummary.includes('Cannot read properties of undefined') || + !reasonSummary.includes('voice-input')) { // Log in development mode only if (this.options.isDevelopment) { - this.logger.debug('[VoiceInput] Ignored external error', error); + this.logger.debug('[VoiceInput] Ignored external error', { reason: reasonSummary }); } return; } this.handleError( - new Error(`Unhandled Promise Rejection: ${event.reason}`), + new Error(`Unhandled Promise Rejection: ${reasonSummary}`), { component: 'Global', operation: 'UnhandledRejection', @@ -159,8 +158,9 @@ export class ErrorHandler implements IDisposable { // Global error events this.errorHandler = (event: ErrorEvent) => { + const eventError = event.error instanceof Error ? event.error : new Error(event.message); this.handleError( - event.error || new Error(event.message), + eventError, { component: 'Global', operation: 'UncaughtError', @@ -247,7 +247,7 @@ export class ErrorHandler implements IDisposable { let contextSummary: string; try { contextSummary = JSON.stringify(context); - } catch (_error) { + } catch { contextSummary = '[Unserializable context]'; } throw lastError || new Error(`Retry operation failed after ${this.options.maxRetries} attempts in context: ${contextSummary}`); @@ -319,6 +319,20 @@ export class ErrorHandler implements IDisposable { } } + private describeReason(reason: unknown): string { + if (typeof reason === 'string') { + return reason; + } + if (reason instanceof Error) { + return reason.message || reason.name; + } + try { + return JSON.stringify(reason); + } catch { + return String(reason); + } + } + /** * ユーザーに通知 */ @@ -343,7 +357,7 @@ export class ErrorHandler implements IDisposable { if (!this.i18n) { try { this.i18n = getI18n(); - } catch (_error) { + } catch { // Fallback to English messages switch (severity) { case ErrorSeverity.FATAL: diff --git a/src/errors/TranscriptionError.ts b/src/errors/TranscriptionError.ts index 61ba645..283260a 100644 --- a/src/errors/TranscriptionError.ts +++ b/src/errors/TranscriptionError.ts @@ -37,7 +37,7 @@ export class TranscriptionError extends Error { try { i18n = getI18n(); - } catch (_error) { + } catch { // Fallback to original message if i18n is not available return this.message; } diff --git a/src/main.ts b/src/main.ts index 598b425..462da75 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,2 +1,2 @@ // Main entry point for Voice Memo Plugin -export { default } from './plugin'; \ No newline at end of file +export { default } from './plugin'; diff --git a/src/managers/ViewManager.ts b/src/managers/ViewManager.ts index cac0282..f28ee5b 100644 --- a/src/managers/ViewManager.ts +++ b/src/managers/ViewManager.ts @@ -21,7 +21,7 @@ export class ViewManager extends Disposable { // ServiceLocatorからLoggerを取得 try { this.logger = createServiceLogger('ViewManager'); - } catch (_error) { + } catch { // フォールバック: ServiceLocatorが初期化されていない場合 // 基本のLoggerインスタンスを作成 this.logger = Logger.getLogger('ViewManager'); diff --git a/src/plugin/VoiceInputPlugin.ts b/src/plugin/VoiceInputPlugin.ts index 3ec3614..17ac668 100644 --- a/src/plugin/VoiceInputPlugin.ts +++ b/src/plugin/VoiceInputPlugin.ts @@ -11,6 +11,14 @@ import { serviceLocator, ServiceKeys, getI18nService } from '../services'; import { getObsidianLocale } from '../types'; import { SafeStorageService } from '../security'; +type LegacyVoiceInputSettings = Partial & { + language?: string; + interfaceLanguage?: string; + recordingMode?: string; + autoStopSilenceDuration?: number; + minSpeechDuration?: number; +}; + export default class VoiceInputPlugin extends Plugin { settings: VoiceInputSettings; private viewManager: ViewManager; @@ -110,7 +118,7 @@ export default class VoiceInputPlugin extends Plugin { // Add ribbon icon this.addRibbonIcon('microphone', i18n.t('ui.titles.main'), () => { this.logger.debug('Ribbon icon clicked'); - this.viewManager.activateVoiceInputView(); + void this.viewManager.activateVoiceInputView(); }); this.logger.debug('Ribbon icon added'); @@ -120,7 +128,7 @@ export default class VoiceInputPlugin extends Plugin { name: i18n.t('ui.commands.openView'), callback: () => { this.logger.debug('Command executed: open-view'); - this.viewManager.activateVoiceInputView(); + void this.viewManager.activateVoiceInputView(); } }); this.logger.debug('Commands registered'); @@ -212,7 +220,7 @@ export default class VoiceInputPlugin extends Plugin { } async loadSettings() { - const data = await this.loadData(); + const data = this.parseStoredSettings(await this.loadData()); // まずデフォルト設定から開始 this.settings = { ...DEFAULT_SETTINGS }; @@ -475,4 +483,11 @@ export default class VoiceInputPlugin extends Plugin { getLogger(): Logger | null { return this.logger; } + + private parseStoredSettings(data: unknown): LegacyVoiceInputSettings | null { + if (typeof data === 'object' && data !== null) { + return data as LegacyVoiceInputSettings; + } + return null; + } } diff --git a/src/security/SafeStorageService.ts b/src/security/SafeStorageService.ts index 0128375..981dd2e 100644 --- a/src/security/SafeStorageService.ts +++ b/src/security/SafeStorageService.ts @@ -34,6 +34,16 @@ const isElectronWindow = (win: Window): win is ElectronWindow => { return 'require' in win && typeof (win as ElectronWindow).require === 'function'; }; +const resolveElectronRenderer = (): ElectronRenderer | null => { + if (isElectronWindow(window) && typeof (window as ElectronWindow).require === 'function') { + return (window as ElectronWindow).require('electron') ?? null; + } + if ((window as ElectronWindow).electron) { + return (window as ElectronWindow).electron ?? null; + } + return (globalThis as ElectronGlobal).electron ?? null; +}; + export class SafeStorageService { private static safeStorage: ElectronSafeStorage | null = null; private static logger: Logger = getLogger('SafeStorageService'); @@ -53,15 +63,10 @@ export class SafeStorageService { this.logger.debug(`window.require exists: ${'require' in window}`); this.logger.debug(`window.require is function: ${typeof (window as ElectronWindow).require === 'function'}`); - const electron = isElectronWindow(window) ? window.require?.('electron') : null; + const electron = resolveElectronRenderer(); if (!electron) { - // Alternative access method - const globalElectron = (window as ElectronWindow).electron || (global as ElectronGlobal).electron; - if (globalElectron) { - this.logger.debug('Found electron via global access'); - this.safeStorage = globalElectron.remote?.safeStorage || globalElectron.safeStorage || null; - } + this.logger.debug('Electron renderer not available'); } else { this.logger.debug(`Electron object exists: ${!!electron}`); this.logger.debug(`Electron.remote exists: ${!!electron?.remote}`); diff --git a/src/services/I18nService.ts b/src/services/I18nService.ts index 4207fac..f550180 100644 --- a/src/services/I18nService.ts +++ b/src/services/I18nService.ts @@ -127,8 +127,10 @@ export class I18nServiceImpl implements I18nService { * Supports {paramName} syntax */ private substituteParams(translation: string, params: Record): string { - return translation.replace(/{(\w+)}/g, (match, paramName) => { - return params[paramName]?.toString() || match; + return translation.replace(/{(\w+)}/g, (match, captured) => { + const paramKey = captured as keyof typeof params; + const value = params[paramKey]; + return value !== undefined ? value.toString() : match; }); } diff --git a/src/utils/ObsidianHttpClient.ts b/src/utils/ObsidianHttpClient.ts index 548aaa7..cdae223 100644 --- a/src/utils/ObsidianHttpClient.ts +++ b/src/utils/ObsidianHttpClient.ts @@ -20,7 +20,8 @@ export class ObsidianHttpClient { }): Promise<{ status: number; json: unknown; text: string }> { const { url, method = 'GET', headers = {}, body } = opts; const res = await requestUrl({ url, method, headers, body, throw: false }); - return { status: res.status, json: res.json, text: res.text }; + const jsonBody: unknown = res.json; + return { status: res.status, json: jsonBody, text: res.text }; } /** @@ -43,7 +44,8 @@ export class ObsidianHttpClient { body: JSON.stringify(data), throw: false }); - return { status: res.status, json: res.json }; + const jsonBody: unknown = res.json; + return { status: res.status, json: jsonBody }; } /** @@ -63,8 +65,8 @@ export class ObsidianHttpClient { body, throw: false }); - - return { status: response.status, json: response.json }; + const jsonBody: unknown = response.json; + return { status: response.status, json: jsonBody }; } /** diff --git a/src/views/VoiceInputViewUI.ts b/src/views/VoiceInputViewUI.ts index 4fb29ef..d00560d 100644 --- a/src/views/VoiceInputViewUI.ts +++ b/src/views/VoiceInputViewUI.ts @@ -33,8 +33,8 @@ export class VoiceInputViewUI { secondRowContainer: HTMLElement; // Settings controls - correctionToggle: ToggleComponent; - transcriptionModelDropdown: DropdownComponent; + correctionToggle: ToggleComponent | null = null; + transcriptionModelDropdown: DropdownComponent | null = null; // Event handlers private clickHandler: ((e: MouseEvent) => void) | null = null;