const { spawn } = require('child_process'); const logger = require('../../utils/logger'); const CLIPBOARD_THRESHOLD = 500 * 1024; class ClipboardService { constructor() { this.threshold = CLIPBOARD_THRESHOLD; } async read() { return new Promise((resolve, reject) => { const psCode = ` Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing if ([Windows.Forms.Clipboard]::ContainsText()) { $text = [Windows.Forms.Clipboard]::GetText() Write-Output "TYPE:TEXT" Write-Output $text } elseif ([Windows.Forms.Clipboard]::ContainsImage()) { $image = [Windows.Forms.Clipboard]::GetImage() if ($image -ne $null) { $ms = New-Object System.IO.MemoryStream $image.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png) $bytes = $ms.ToArray() $base64 = [Convert]::ToBase64String($bytes) Write-Output "TYPE:IMAGE" Write-Output $base64 } } else { Write-Output "TYPE:EMPTY" } `; const ps = spawn('powershell', ['-NoProfile', '-Command', '[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; ' + psCode ], { encoding: 'utf8' }); let output = ''; ps.stdout.on('data', (data) => { output += data.toString(); }); ps.stderr.on('data', (data) => { logger.error('Clipboard read error', { error: data.toString() }); }); ps.on('close', () => { const lines = output.trim().split('\n'); const typeLine = lines[0]; if (typeLine && typeLine.includes('TYPE:TEXT')) { const text = lines.slice(1).join('\n'); resolve({ type: 'text', data: text, size: Buffer.byteLength(text, 'utf8') }); } else if (typeLine && typeLine.includes('TYPE:IMAGE')) { const base64 = lines.slice(1).join(''); const size = Math.ceil(base64.length * 0.75); resolve({ type: 'image', data: base64, size: size }); } else { resolve({ type: 'empty', data: null, size: 0 }); } }); ps.on('error', (err) => { logger.error('Clipboard read failed', { error: err.message }); resolve({ type: 'empty', data: null, size: 0 }); }); }); } async set(content) { return new Promise((resolve, reject) => { let psCode; if (content.type === 'text') { const escapedText = content.data .replace(/'/g, "''") .replace(/\r\n/g, '`r`n') .replace(/\n/g, '`n'); psCode = ` $OutputEncoding = [Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8 Add-Type -AssemblyName System.Windows.Forms [Windows.Forms.Clipboard]::SetText('${escapedText}') Write-Output "SUCCESS" `; } else if (content.type === 'image') { psCode = ` $OutputEncoding = [Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8 Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing $bytes = [Convert]::FromBase64String('${content.data}') $ms = New-Object System.IO.MemoryStream(,$bytes) $image = [System.Drawing.Image]::FromStream($ms) [Windows.Forms.Clipboard]::SetImage($image) Write-Output "SUCCESS" `; } else { resolve(false); return; } const ps = spawn('powershell', ['-NoProfile', '-Command', psCode]); let output = ''; ps.stdout.on('data', (data) => { output += data.toString(); }); ps.on('close', () => { resolve(output.includes('SUCCESS')); }); ps.on('error', () => resolve(false)); }); } isSmallContent(size) { return size <= this.threshold; } } module.exports = ClipboardService;