diff --git a/.omni/e42f8a19-untitled/memory.md b/.omni/e42f8a19-untitled/memory.md new file mode 100644 index 0000000..ef4f66c --- /dev/null +++ b/.omni/e42f8a19-untitled/memory.md @@ -0,0 +1,23 @@ +# Workspace Context + + + + +**Workspace root (absolute path):** `/home/workspaces/conversations/e42f8a19-5d65-46b6-a008-ae3e31a703c2` + +## Repositories + +- **`proofsnap-extension/`** — Branch: `omni/e42f8a19/proofsnap-extension`, Remote: `numbersprotocol/proofsnap-extension` + - Snap once. Prove forever. Turn your browser into a trust engine—sealing screenshots with cryptographic proof. + +## Environment & Tools + +- `proofsnap-extension/`: React 19 + TypeScript Chrome MV3 extension built with Vite; use `npm ci`, `npm run type-check`, `npm run lint`, and `npm run build`. +- Build requires `manifest.json`; generate it from `manifest.template.json` with `OAUTH_CLIENT_ID` and `EXTENSION_KEY` before `npm run build`. + +## Key Discoveries + +- Selection capture is coordinated in `src/background/service-worker.ts`: popup sends `CAPTURE_SCREENSHOT`, service worker injects the selection overlay, then receives `SELECTION_COMPLETE` and crops via the offscreen document. + +--- +_Last system refresh: 2026-05-06 06:33 UTC_ diff --git a/src/background/service-worker.ts b/src/background/service-worker.ts index 6b6f540..46fb494 100644 --- a/src/background/service-worker.ts +++ b/src/background/service-worker.ts @@ -223,6 +223,204 @@ function rejectPendingSelection(error: unknown): void { resetPendingSelectionState(); } +function getErrorMessage(error: unknown): string { + if (error instanceof Error) return error.message; + if (error && typeof error === 'object') { + const maybeMessage = (error as { message?: unknown }).message; + if (typeof maybeMessage === 'string') return maybeMessage; + } + return String(error); +} + +function startProofSnapSelectionOverlay(): void { + if ((window as any).__proofSnapSelectionActive) { + return; + } + + (window as any).__proofSnapSelectionActive = true; + + type SelectionCoordinates = { + x: number; + y: number; + width: number; + height: number; + }; + + let overlay: HTMLDivElement | null = null; + let selectionBox: HTMLDivElement | null = null; + let isSelecting = false; + let startX = 0; + let startY = 0; + + function cleanup(): void { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + document.removeEventListener('keydown', handleKeyDown); + + ['proofsnap-selection-overlay', 'proofsnap-selection-box', 'proofsnap-instructions'].forEach((id) => { + document.getElementById(id)?.remove(); + }); + + overlay = null; + selectionBox = null; + (window as any).__proofSnapSelectionActive = false; + } + + function sendResponse(data: any): void { + chrome.runtime.sendMessage({ + type: 'SELECTION_COMPLETE', + payload: data, + }); + } + + function handleMouseDown(e: MouseEvent): void { + if (e.button !== 0) return; + + isSelecting = true; + startX = e.clientX; + startY = e.clientY; + + if (selectionBox) { + selectionBox.style.display = 'block'; + selectionBox.style.left = `${startX}px`; + selectionBox.style.top = `${startY}px`; + selectionBox.style.width = '0px'; + selectionBox.style.height = '0px'; + } + + if (overlay) { + overlay.style.background = 'transparent'; + } + } + + function handleMouseMove(e: MouseEvent): void { + if (!isSelecting || !selectionBox) return; + + const currentX = e.clientX; + const currentY = e.clientY; + const left = Math.min(startX, currentX); + const top = Math.min(startY, currentY); + const width = Math.abs(currentX - startX); + const height = Math.abs(currentY - startY); + + selectionBox.style.left = `${left}px`; + selectionBox.style.top = `${top}px`; + selectionBox.style.width = `${width}px`; + selectionBox.style.height = `${height}px`; + } + + function handleMouseUp(e: MouseEvent): void { + if (!isSelecting) return; + + isSelecting = false; + + const currentX = e.clientX; + const currentY = e.clientY; + const left = Math.min(startX, currentX); + const top = Math.min(startY, currentY); + const width = Math.abs(currentX - startX); + const height = Math.abs(currentY - startY); + + if (width < 10 || height < 10) { + cleanup(); + sendResponse({ cancelled: true, reason: 'Selection too small' }); + return; + } + + const dpr = window.devicePixelRatio || 1; + const coordinates: SelectionCoordinates = { + x: Math.round(left * dpr), + y: Math.round(top * dpr), + width: Math.round(width * dpr), + height: Math.round(height * dpr), + }; + + cleanup(); + sendResponse({ + cancelled: false, + coordinates, + viewportCoordinates: { x: left, y: top, width, height }, + }); + } + + function handleKeyDown(e: KeyboardEvent): void { + if (e.key === 'Escape') { + cleanup(); + sendResponse({ cancelled: true, reason: 'User cancelled' }); + } + } + + function initSelectionOverlay(): void { + overlay = document.createElement('div'); + overlay.id = 'proofsnap-selection-overlay'; + overlay.style.cssText = ` + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(0, 0, 0, 0.5); + z-index: 2147483647; + cursor: crosshair; + user-select: none; + `; + + selectionBox = document.createElement('div'); + selectionBox.id = 'proofsnap-selection-box'; + selectionBox.style.cssText = ` + position: fixed; + border: 2px dashed #fff; + background: transparent; + box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5); + z-index: 2147483647; + display: none; + pointer-events: none; + `; + + const instructions = document.createElement('div'); + instructions.id = 'proofsnap-instructions'; + instructions.innerHTML = ` +
+ ProofSnap - Click and drag to select area. Press Esc to cancel. +
+ `; + + document.body.appendChild(overlay); + document.body.appendChild(selectionBox); + document.body.appendChild(instructions); + + overlay.addEventListener('mousedown', handleMouseDown); + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + document.addEventListener('keydown', handleKeyDown); + } + + if (document.body) { + initSelectionOverlay(); + } else { + document.addEventListener('DOMContentLoaded', initSelectionOverlay, { once: true }); + } +} + /** * Handle selection mode capture * Injects content script and waits for user selection @@ -246,13 +444,21 @@ async function handleSelectionCapture(tab: chrome.tabs.Tab, fromPopup: boolean): // Inject the selection overlay content script try { + try { + await chrome.windows.update(tab.windowId, { focused: true }); + await chrome.tabs.update(tab.id, { active: true }); + } catch (focusError) { + logger.warn('Could not focus selection target before injection:', getErrorMessage(focusError)); + } + await chrome.scripting.executeScript({ target: { tabId: tab.id }, - files: ['content/selection-overlay.js'], + func: startProofSnapSelectionOverlay, }); } catch (error) { - logger.error('Failed to inject selection script:', error); - throw new Error('Failed to start selection mode. Make sure you are on a valid web page.'); + const errorMessage = getErrorMessage(error); + logger.error('Failed to inject selection script:', errorMessage); + throw new Error(`Failed to start selection mode: ${errorMessage}`); } const selectionTarget: PendingSelectionTarget = { diff --git a/src/content/selection-overlay.ts b/src/content/selection-overlay.ts deleted file mode 100644 index 1f65c6b..0000000 --- a/src/content/selection-overlay.ts +++ /dev/null @@ -1,224 +0,0 @@ -/** - * Selection Overlay Content Script - * Allows users to select a region of the page for screenshot capture - */ - -// Prevent multiple injections -if (!(window as any).__proofSnapSelectionActive) { - (window as any).__proofSnapSelectionActive = true; - - interface SelectionCoordinates { - x: number; - y: number; - width: number; - height: number; - } - - let overlay: HTMLDivElement | null = null; - let selectionBox: HTMLDivElement | null = null; - let isSelecting = false; - let startX = 0; - let startY = 0; - - /** - * Initialize the selection overlay - */ - function initSelectionOverlay(): void { - // Create dark overlay - overlay = document.createElement('div'); - overlay.id = 'proofsnap-selection-overlay'; - overlay.style.cssText = ` - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - background: rgba(0, 0, 0, 0.5); - z-index: 2147483647; - cursor: crosshair; - user-select: none; - `; - - // Create selection box - selectionBox = document.createElement('div'); - selectionBox.id = 'proofsnap-selection-box'; - selectionBox.style.cssText = ` - position: fixed; - border: 2px dashed #fff; - background: transparent; - box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5); - z-index: 2147483647; - display: none; - pointer-events: none; - `; - - // Create instructions tooltip - const instructions = document.createElement('div'); - instructions.id = 'proofsnap-instructions'; - instructions.innerHTML = ` -
- ProofSnap - Click and drag to select area. Press Esc to cancel. -
- `; - - document.body.appendChild(overlay); - document.body.appendChild(selectionBox); - document.body.appendChild(instructions); - - // Add event listeners - overlay.addEventListener('mousedown', handleMouseDown); - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleMouseUp); - document.addEventListener('keydown', handleKeyDown); - } - - /** - * Handle mouse down - start selection - */ - function handleMouseDown(e: MouseEvent): void { - if (e.button !== 0) return; // Only left click - - isSelecting = true; - startX = e.clientX; - startY = e.clientY; - - if (selectionBox) { - selectionBox.style.display = 'block'; - selectionBox.style.left = `${startX}px`; - selectionBox.style.top = `${startY}px`; - selectionBox.style.width = '0px'; - selectionBox.style.height = '0px'; - } - - // Hide overlay background while selecting (selection box has its own shadow) - if (overlay) { - overlay.style.background = 'transparent'; - } - } - - /** - * Handle mouse move - update selection box - */ - function handleMouseMove(e: MouseEvent): void { - if (!isSelecting || !selectionBox) return; - - const currentX = e.clientX; - const currentY = e.clientY; - - const left = Math.min(startX, currentX); - const top = Math.min(startY, currentY); - const width = Math.abs(currentX - startX); - const height = Math.abs(currentY - startY); - - selectionBox.style.left = `${left}px`; - selectionBox.style.top = `${top}px`; - selectionBox.style.width = `${width}px`; - selectionBox.style.height = `${height}px`; - } - - /** - * Handle mouse up - complete selection - */ - function handleMouseUp(e: MouseEvent): void { - if (!isSelecting) return; - - isSelecting = false; - - const currentX = e.clientX; - const currentY = e.clientY; - - const left = Math.min(startX, currentX); - const top = Math.min(startY, currentY); - const width = Math.abs(currentX - startX); - const height = Math.abs(currentY - startY); - - // Minimum selection size - if (width < 10 || height < 10) { - cleanup(); - sendResponse({ cancelled: true, reason: 'Selection too small' }); - return; - } - - // Calculate coordinates relative to page (accounting for scroll and device pixel ratio) - const dpr = window.devicePixelRatio || 1; - const coordinates: SelectionCoordinates = { - x: Math.round(left * dpr), - y: Math.round(top * dpr), - width: Math.round(width * dpr), - height: Math.round(height * dpr), - }; - - cleanup(); - sendResponse({ - cancelled: false, - coordinates, - viewportCoordinates: { x: left, y: top, width, height } - }); - } - - /** - * Handle key down - cancel on Escape - */ - function handleKeyDown(e: KeyboardEvent): void { - if (e.key === 'Escape') { - cleanup(); - sendResponse({ cancelled: true, reason: 'User cancelled' }); - } - } - - /** - * Send response back to service worker - */ - function sendResponse(data: any): void { - chrome.runtime.sendMessage({ - type: 'SELECTION_COMPLETE', - payload: data, - }); - } - - /** - * Cleanup overlay elements - */ - function cleanup(): void { - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUp); - document.removeEventListener('keydown', handleKeyDown); - - const elements = [ - 'proofsnap-selection-overlay', - 'proofsnap-selection-box', - 'proofsnap-instructions', - ]; - - elements.forEach((id) => { - const el = document.getElementById(id); - if (el) el.remove(); - }); - - overlay = null; - selectionBox = null; - (window as any).__proofSnapSelectionActive = false; - } - - // Initialize on load - initSelectionOverlay(); -} diff --git a/vite.config.ts b/vite.config.ts index b5839c4..279ae10 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -41,8 +41,7 @@ export default defineConfig({ options: resolve(__dirname, 'src/options/options.html'), background: resolve(__dirname, 'src/background/service-worker.ts'), offscreen: resolve(__dirname, 'src/offscreen/offscreen.ts'), - share: resolve(__dirname, 'src/share/share.tsx'), - 'content/selection-overlay': resolve(__dirname, 'src/content/selection-overlay.ts') + share: resolve(__dirname, 'src/share/share.tsx') }, output: { entryFileNames: '[name].js',