Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/assets/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
89 changes: 89 additions & 0 deletions src/main/bookmarks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { app } from 'electron'
import { readFileSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'
import type { Bookmark } from '../shared/events'

// ============================================================================
// Store de bookmarks — persistido como JSON en la carpeta de datos de la app
// (userData), así sobreviven reinicios. La barra retráctil guarda su estado acá
// mismo para reabrir la app como se dejó.
// ============================================================================

interface Store {
items: Bookmark[]
barVisible: boolean
}

const file = (): string => join(app.getPath('userData'), 'bookmarks.json')
let cache: Store | null = null
let seq = 0

function load(): Store {
if (cache) return cache
try {
const parsed = JSON.parse(readFileSync(file(), 'utf8')) as Partial<Store>
cache = {
items: Array.isArray(parsed.items) ? parsed.items : [],
barVisible: parsed.barVisible === true
}
} catch {
// Primer arranque o archivo inválido: se empieza vacío.
cache = { items: [], barVisible: false }
}
return cache
}

function persist(): void {
try {
writeFileSync(file(), JSON.stringify(load(), null, 2))
} catch (err) {
console.error('[bookmarks] no se pudo guardar:', err)
}
}

// Compara URLs ignorando la barra final: guardar example.com y example.com/ es lo mismo.
const norm = (url: string): string => url.replace(/\/+$/, '')

function makeId(): string {
return `bm-${Date.now().toString(36)}-${(seq++).toString(36)}`
}

export function listBookmarks(): Bookmark[] {
return load().items
}

export function isBarVisible(): boolean {
return load().barVisible
}

export function isSaved(url: string): boolean {
return load().items.some((b) => norm(b.url) === norm(url))
}

/** Guarda la URL si no estaba; la quita si ya estaba. Devuelve el nuevo estado. */
export function toggleBookmark(url: string, title: string): boolean {
const s = load()
const i = s.items.findIndex((b) => norm(b.url) === norm(url))
if (i >= 0) {
s.items.splice(i, 1)
persist()
return false
}
s.items.push({ id: makeId(), url, title: title || url })
persist()
return true
}

export function removeBookmark(id: string): void {
const s = load()
s.items = s.items.filter((b) => b.id !== id)
persist()
}

/** Alterna la barra retráctil y devuelve si quedó visible. */
export function toggleBar(): boolean {
const s = load()
s.barVisible = !s.barVisible
persist()
return s.barVisible
}
36 changes: 28 additions & 8 deletions src/main/cdp/attach.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,37 @@ import { createPerformancePoller } from '../events/performance'

const PROTOCOL_VERSION = '1.3'

export function attachCdp(page: WebContents): void {
// Adjunta CDP a una página y devuelve un `dispose` que revierte todo (detach del
// debugger, pollers y listeners). Solo la pestaña ACTIVA está adjunta a la vez: el
// normalizador de red es un stream único, así que dos tabs adjuntas se pisarían.
export function attachCdp(page: WebContents): () => void {
const dbg = page.debugger
try {
if (!dbg.isAttached()) dbg.attach(PROTOCOL_VERSION)
} catch (err) {
console.error('[cdp] no se pudo adjuntar el debugger:', err)
return
return () => {}
}

// Rutea cada mensaje CDP al normalizador de su dominio.
dbg.on('message', (_event, method, params) => {
const onMessage = (_event: unknown, method: string, params: unknown): void => {
if (method.startsWith('Network.')) handleCdpNetwork(method, params)
else if (method === 'Runtime.consoleAPICalled' || method === 'Runtime.exceptionThrown' || method === 'Log.entryAdded') {
handleCdpConsole(method, params)
}
// Próximas fases: HeapProfiler/Memory → memory, Performance → performance, etc.
})
}
dbg.on('message', onMessage)

const memory = createMemoryPoller(dbg)
const performance = createPerformancePoller(dbg)

dbg.on('detach', (_event, reason) => {
const onDetach = (_event: unknown, reason: string): void => {
console.warn('[cdp] debugger detached:', reason)
memory.stop()
performance.stop()
})
}
dbg.on('detach', onDetach)

// Dominios habilitados: Network (D-015) + Runtime/Log (REQ-021) + Performance (REQ-023).
dbg.sendCommand('Network.enable').catch((e) => console.error('[cdp] Network.enable', e))
Expand All @@ -52,7 +57,22 @@ export function attachCdp(page: WebContents): void {
performance.start()

// Al navegar a otra página, limpiamos el estado de red acumulado.
page.on('did-start-navigation', (_e, _url, isInPlace, isMainFrame) => {
const onNavigation = (_e: unknown, _url: string, isInPlace: boolean, isMainFrame: boolean): void => {
if (isMainFrame && !isInPlace) resetNetwork()
})
}
page.on('did-start-navigation', onNavigation)

return () => {
memory.stop()
performance.stop()
dbg.off('message', onMessage)
dbg.off('detach', onDetach)
if (!page.isDestroyed()) page.off('did-start-navigation', onNavigation)
try {
if (dbg.isAttached()) dbg.detach()
} catch {
// El webContents pudo destruirse antes; detach ya no aplica.
}
resetNetwork()
}
}
6 changes: 5 additions & 1 deletion src/main/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { app, BaseWindow, Menu } from 'electron'
import { electronApp } from '@electron-toolkit/utils'
import { electronApp, is } from '@electron-toolkit/utils'
import { createWindow } from './window'

// El aviso de CSP inseguro de Electron es ruido en dev: aplica a cada página
// externa que se inspecciona (no controlamos su CSP) y no aparece empaquetado.
if (is.dev) process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'

// Sin barra de menú nativa (File/Edit/View…). Overrun usa su propio chrome.
Menu.setApplicationMenu(null)

Expand Down
Loading
Loading