import { contextBridge, ipcRenderer } from 'electron' export type { ChangePasswordResult, CreateNoteInput, CreateTagAndAssignInput, CreateTagInput, CreateVaultResult, GeneratedKeyFile, IpcResult, KeyFileOperationResult, KeyFileSelection, Note, NoteFilter, NoteListItem, NotePatch, NoteTagPair, NotvexAPI, Platform, Prefs, RecentVault, DownloadProgress, Tag, TagPatch, UnlockThrottleStatus, UpdateInfo, VaultStatus, VaultVersion } from '@shared/types' import type { DownloadProgress, NotvexAPI, Platform, UpdateInfo } from '@shared/types' // ─── Implementation ──────────────────────────────────────────────────────────── const api: NotvexAPI = { platform: process.platform as Platform, vault: { hasVault: (filePath) => ipcRenderer.invoke('vault:has-vault', filePath), create: (filePath, pw) => ipcRenderer.invoke('vault:create', filePath, pw), open: (filePath, pw, keyFileContents) => ipcRenderer.invoke('vault:open', filePath, pw, keyFileContents), openWithRecovery: (filePath, m, keyFileContents) => ipcRenderer.invoke('vault:open-with-recovery', filePath, m, keyFileContents), changePassword: (cur, next, keyFileContents) => ipcRenderer.invoke('vault:change-password', cur, next, keyFileContents), rotateCredentials: (pw) => ipcRenderer.invoke('vault:rotate-credentials', pw), regenerateRecoveryKey: (pw, keyFileContents) => ipcRenderer.invoke('vault:regenerate-recovery-key', pw, keyFileContents), confirmRecoverySaved: () => ipcRenderer.invoke('vault:confirm-recovery-saved'), recoveryUnconfirmed: () => ipcRenderer.invoke('vault:recovery-unconfirmed'), close: (createPath) => ipcRenderer.invoke('vault:close', createPath), clearDecryptedContent: () => ipcRenderer.invoke('vault:clear-decrypted'), status: () => ipcRenderer.invoke('vault:status'), switchTo: (filePath) => ipcRenderer.invoke('vault:switch', filePath), chooseFile: (mode) => ipcRenderer.invoke('vault:choose-file', mode), selectKeyFile: () => ipcRenderer.invoke('vault:select-key-file'), getUnlockThrottleStatus: () => ipcRenderer.invoke('vault:unlock-throttle-status'), getHasKeyFile: () => ipcRenderer.invoke('vault:get-has-key-file'), generateKeyFile: () => ipcRenderer.invoke('vault:generate-key-file'), configureKeyFile: (password, keyFileContents) => ipcRenderer.invoke('vault:configure-key-file', password, keyFileContents), removeKeyFile: (password, keyFileContents) => ipcRenderer.invoke('vault:remove-key-file', password, keyFileContents), saveCopyAs: () => ipcRenderer.invoke('vault:save-copy-as'), confirmMigration: (createBackup) => ipcRenderer.invoke('vault:migration-confirmed', createBackup), cancelMigration: () => ipcRenderer.invoke('vault:migration-cancelled'), getPendingFile: () => ipcRenderer.invoke('vault:get-pending-file'), onMigrationRequired: (callback) => { const listener = ( _e: unknown, data: { reason: 'header' | 'schema' vaultPath: string backupTimestamp: number fromVersion: number toVersion: number } ): void => callback(data) ipcRenderer.on('vault:migration-required', listener) return (): void => { ipcRenderer.off('vault:migration-required', listener) } }, confirmDevBuildWarning: () => ipcRenderer.invoke('vault:dev-build-warning-confirmed'), cancelDevBuildWarning: () => ipcRenderer.invoke('vault:dev-build-warning-cancelled'), onDevBuildWarningRequired: (callback) => { const listener = (_e: unknown, data: { vaultPath: string }): void => callback(data) ipcRenderer.on('vault:dev-build-warning-required', listener) return (): void => { ipcRenderer.off('vault:dev-build-warning-required', listener) } }, openBackupsFolder: () => ipcRenderer.invoke('vault:open-backups-folder') }, notes: { create: (input) => ipcRenderer.invoke('notes:create', input), get: (id) => ipcRenderer.invoke('notes:get', id), list: (filter) => ipcRenderer.invoke('notes:list', filter), update: (id, patch) => ipcRenderer.invoke('notes:update', id, patch), duplicate: (id) => ipcRenderer.invoke('notes:duplicate', id), trash: (id) => ipcRenderer.invoke('notes:trash', id), restore: (id) => ipcRenderer.invoke('notes:restore', id), delete: (id) => ipcRenderer.invoke('notes:delete', id), emptyTrash: () => ipcRenderer.invoke('notes:empty-trash'), counts: () => ipcRenderer.invoke('notes:counts'), export: (id) => ipcRenderer.invoke('notes:export', id), exportAll: () => ipcRenderer.invoke('notes:export-all'), import: () => ipcRenderer.invoke('notes:import') }, tags: { create: (input) => ipcRenderer.invoke('tags:create', input), createAndAssign: (input) => ipcRenderer.invoke('tags:create-and-assign', input), list: () => ipcRenderer.invoke('tags:list'), update: (id, patch) => ipcRenderer.invoke('tags:update', id, patch), delete: (id) => ipcRenderer.invoke('tags:delete', id) }, noteTags: { add: (n, t) => ipcRenderer.invoke('note-tags:add', n, t), remove: (n, t) => ipcRenderer.invoke('note-tags:remove', n, t), list: (n) => ipcRenderer.invoke('note-tags:list', n), counts: () => ipcRenderer.invoke('note-tags:counts'), all: () => ipcRenderer.invoke('note-tags:all') }, prefs: { get: () => ipcRenderer.invoke('prefs:get'), set: (key, value) => ipcRenderer.invoke('prefs:set', key, value) }, clipboard: { write: (text, opts) => ipcRenderer.invoke('clipboard:write', text, opts) }, shell: { openExternal: (url) => ipcRenderer.invoke('shell:open-external', url) }, log: { error: (payload) => ipcRenderer.invoke('log:error', payload), buildReport: () => ipcRenderer.invoke('log:build-report'), openFolder: () => ipcRenderer.invoke('log:open-folder'), clear: () => ipcRenderer.invoke('log:clear') }, app: { isDev: () => ipcRenderer.invoke('app:is-dev'), reloadWindow: () => ipcRenderer.invoke('app:reload-window') }, updater: { onUpdateAvailable: (callback: (info: UpdateInfo) => void) => { const listener = (_e: unknown, data: UpdateInfo): void => callback(data) ipcRenderer.on('updater:update-available', listener) return (): void => { ipcRenderer.off('updater:update-available', listener) } }, onUpdateNotAvailable: (callback: () => void) => { const listener = (): void => callback() ipcRenderer.on('updater:update-not-available', listener) return (): void => { ipcRenderer.off('updater:update-not-available', listener) } }, onDownloadProgress: (callback: (progress: DownloadProgress) => void) => { const listener = (_e: unknown, data: DownloadProgress): void => callback(data) ipcRenderer.on('updater:download-progress', listener) return (): void => { ipcRenderer.off('updater:download-progress', listener) } }, onUpdateDownloaded: (callback: () => void) => { const listener = (): void => callback() ipcRenderer.on('updater:update-downloaded', listener) return (): void => { ipcRenderer.off('updater:update-downloaded', listener) } }, onError: (callback: (error: { message: string }) => void) => { const listener = (_e: unknown, data: { message: string }): void => callback(data) ipcRenderer.on('updater:error', listener) return (): void => { ipcRenderer.off('updater:error', listener) } }, checkNow: () => ipcRenderer.invoke('updater:check-now'), checkOnStartup: () => ipcRenderer.invoke('updater:check-on-startup'), download: () => ipcRenderer.invoke('updater:download'), installNow: () => ipcRenderer.invoke('updater:install-now'), getCurrentVersion: () => ipcRenderer.invoke('updater:get-current-version') }, onWillLock: (callback) => { const listener = (_e: unknown, requestId?: number): void => { // Always ack, even on failure: a rejected flush must not hold the lock // hostage until the main process times out. The catch is what keeps a // failed flush from surfacing as an unhandled rejection. The id is // echoed back so this ack can't resolve a different, concurrent drain. void callback() .catch(() => undefined) .finally(() => ipcRenderer.send('vault:flush-complete', requestId)) } ipcRenderer.on('vault:will-lock', listener) return (): void => { ipcRenderer.off('vault:will-lock', listener) } }, onAutoLocked: (callback) => { const listener = (): void => callback() ipcRenderer.on('vault:auto-locked', listener) return (): void => { ipcRenderer.off('vault:auto-locked', listener) } }, onPackFailed: (callback) => { const listener = (): void => callback() ipcRenderer.on('vault:pack-failed', listener) return (): void => { ipcRenderer.off('vault:pack-failed', listener) } }, onReloadRequested: (callback) => { const listener = (): void => callback() ipcRenderer.on('app:reload-requested', listener) return (): void => { ipcRenderer.off('app:reload-requested', listener) } }, onOpenFile: (callback) => { const listener = (_e: unknown, filePath: string): void => callback(filePath) ipcRenderer.on('vault:open-file', listener) return (): void => { ipcRenderer.off('vault:open-file', listener) } } } contextBridge.exposeInMainWorld('notvex', api)