| 1 | import { ipcMain } from 'electron' |
| 2 | import log from 'electron-log/main.js' |
| 3 | import type { IpcContext } from '../ipc/context' |
| 4 | import { |
| 5 | listMergeSourcePages, |
| 6 | listMergeSourceSessions, |
| 7 | listMergeSourceTemplatePages, |
| 8 | listMergeSourceTemplates, |
| 9 | mergeSessionPages |
| 10 | } from './page-merge-service' |
| 11 | import { PageMergeError, type PageMergeErrorCode } from '../../shared/page-merge' |
| 12 | |
| 13 | const readString = (record: Record<string, unknown>, key: string): string => |
| 14 | typeof record[key] === 'string' ? record[key].trim() : '' |
| 15 | |
| 16 | const runPageMergeRequest = async <T>( |
| 17 | channel: string, |
| 18 | context: Record<string, unknown>, |
| 19 | task: () => Promise<T> |
| 20 | ): Promise<T> => { |
| 21 | const startedAt = Date.now() |
| 22 | log.info('[page-merge:ipc]', { channel, stage: 'start', ...context }) |
| 23 | try { |
| 24 | const result = await task() |
| 25 | log.info('[page-merge:ipc]', { |
| 26 | channel, |
| 27 | stage: 'completed', |
| 28 | durationMs: Date.now() - startedAt, |
| 29 | ...context |
| 30 | }) |
| 31 | return result |
| 32 | } catch (error) { |
| 33 | const code: PageMergeErrorCode = |
| 34 | error instanceof PageMergeError ? error.code : 'PAGE_MERGE_INTERNAL_ERROR' |
| 35 | log.warn('[page-merge:ipc]', { |
| 36 | channel, |
| 37 | stage: 'failed', |
| 38 | code, |
| 39 | durationMs: Date.now() - startedAt, |
| 40 | ...context, |
| 41 | error: error instanceof Error ? error.message : String(error) |
| 42 | }) |
| 43 | throw new Error(code) |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | export function registerPageMergeHandlers(ctx: IpcContext): void { |
| 48 | ipcMain.handle('session:listMergeSources', async (_event, payload: unknown) => { |
| 49 | const record = |
| 50 | payload && typeof payload === 'object' ? (payload as Record<string, unknown>) : {} |
| 51 | const targetSessionId = readString(record, 'targetSessionId') |
| 52 | if (!targetSessionId) throw new Error('PAGE_MERGE_INVALID_REQUEST') |
| 53 | return runPageMergeRequest('session:listMergeSources', { targetSessionId }, () => |
| 54 | listMergeSourceSessions(ctx, targetSessionId) |
| 55 | ) |
| 56 | }) |
| 57 | |
| 58 | ipcMain.handle('session:listMergeSourceTemplates', async (_event, payload: unknown) => { |
| 59 | const record = |
| 60 | payload && typeof payload === 'object' ? (payload as Record<string, unknown>) : {} |
| 61 | const targetSessionId = readString(record, 'targetSessionId') |
| 62 | if (!targetSessionId) throw new Error('PAGE_MERGE_INVALID_REQUEST') |
| 63 | return runPageMergeRequest('session:listMergeSourceTemplates', { targetSessionId }, () => |
| 64 | listMergeSourceTemplates(ctx, targetSessionId) |
| 65 | ) |
| 66 | }) |
| 67 | |
| 68 | ipcMain.handle('session:listMergeSourcePages', async (_event, payload: unknown) => { |
| 69 | const record = |
| 70 | payload && typeof payload === 'object' ? (payload as Record<string, unknown>) : {} |
| 71 | const targetSessionId = readString(record, 'targetSessionId') |
| 72 | const sourceType = readString(record, 'sourceType') || 'session' |
| 73 | if (!targetSessionId) throw new Error('PAGE_MERGE_INVALID_REQUEST') |
| 74 | if (sourceType === 'template') { |
| 75 | const templateId = readString(record, 'templateId') |
| 76 | if (!templateId) throw new Error('PAGE_MERGE_INVALID_REQUEST') |
| 77 | return runPageMergeRequest( |
| 78 | 'session:listMergeSourcePages', |
| 79 | { targetSessionId, sourceType, templateId }, |
| 80 | () => listMergeSourceTemplatePages(ctx, targetSessionId, templateId) |
| 81 | ) |
| 82 | } |
| 83 | const sourceSessionId = readString(record, 'sourceSessionId') |
| 84 | if (!sourceSessionId) throw new Error('PAGE_MERGE_INVALID_REQUEST') |
| 85 | if (targetSessionId === sourceSessionId) throw new Error('PAGE_MERGE_SAME_SESSION') |
| 86 | return runPageMergeRequest( |
| 87 | 'session:listMergeSourcePages', |
| 88 | { targetSessionId, sourceSessionId }, |
| 89 | () => listMergeSourcePages(ctx, sourceSessionId) |
| 90 | ) |
| 91 | }) |
| 92 | |
| 93 | ipcMain.handle('session:mergePages', async (_event, payload: unknown) => { |
| 94 | const record = |
| 95 | payload && typeof payload === 'object' ? (payload as Record<string, unknown>) : {} |
| 96 | const targetSessionId = readString(record, 'targetSessionId') |
| 97 | const sourceType = readString(record, 'sourceType') || 'session' |
| 98 | const sourcePageIds = record.sourcePageIds |
| 99 | if ( |
| 100 | !targetSessionId || |
| 101 | !Array.isArray(sourcePageIds) || |
| 102 | sourcePageIds.some((item) => typeof item !== 'string') |
| 103 | ) { |
| 104 | throw new Error('PAGE_MERGE_INVALID_REQUEST') |
| 105 | } |
| 106 | const selectedPageCount = sourcePageIds.length |
| 107 | if (sourceType === 'template') { |
| 108 | const sourceTemplateId = readString(record, 'templateId') |
| 109 | if (!sourceTemplateId) throw new Error('PAGE_MERGE_INVALID_REQUEST') |
| 110 | return runPageMergeRequest( |
| 111 | 'session:mergePages', |
| 112 | { targetSessionId, sourceType, sourceTemplateId, selectedPageCount }, |
| 113 | async () => { |
| 114 | const result = await mergeSessionPages(ctx, { |
| 115 | targetSessionId, |
| 116 | sourceType: 'template', |
| 117 | sourceTemplateId, |
| 118 | sourcePageIds: sourcePageIds as string[] |
| 119 | }) |
| 120 | return { ok: true, ...result } |
| 121 | } |
| 122 | ) |
| 123 | } |
| 124 | const sourceSessionId = readString(record, 'sourceSessionId') |
| 125 | if (!sourceSessionId) throw new Error('PAGE_MERGE_INVALID_REQUEST') |
| 126 | return runPageMergeRequest( |
| 127 | 'session:mergePages', |
| 128 | { targetSessionId, sourceType, sourceSessionId, selectedPageCount }, |
| 129 | async () => { |
| 130 | const result = await mergeSessionPages(ctx, { |
| 131 | targetSessionId, |
| 132 | sourceType: 'session', |
| 133 | sourceSessionId, |
| 134 | sourcePageIds: sourcePageIds as string[] |
| 135 | }) |
| 136 | return { ok: true, ...result } |
| 137 | } |
| 138 | ) |
| 139 | }) |
| 140 | } |
| 141 |