Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import { createNoteDerivedStateProjector } from '../../projections/projectors/no
vi.mock('electron', () => {
const send = vi.fn()
return {
BrowserWindow: { getAllWindows: vi.fn(() => [{ webContents: { send } }]) },
BrowserWindow: {
getAllWindows: vi.fn(() => [
{ isDestroyed: () => false, webContents: { isDestroyed: () => false, send } }
])
},
shell: { openPath: vi.fn(() => Promise.resolve('')), showItemInFolder: vi.fn() }
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ import { createNoteDerivedStateProjector } from '../../projections/projectors/no
vi.mock('electron', () => {
const send = vi.fn()
return {
BrowserWindow: { getAllWindows: vi.fn(() => [{ webContents: { send } }]) },
BrowserWindow: {
getAllWindows: vi.fn(() => [
{ isDestroyed: () => false, webContents: { isDestroyed: () => false, send } }
])
},
shell: { openPath: vi.fn(() => Promise.resolve('')), showItemInFolder: vi.fn() }
}
})
Expand Down
6 changes: 5 additions & 1 deletion apps/desktop/src/main/import/bear/bear-importer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ import { createNoteDerivedStateProjector } from '../../projections/projectors/no
vi.mock('electron', () => {
const send = vi.fn()
return {
BrowserWindow: { getAllWindows: vi.fn(() => [{ webContents: { send } }]) },
BrowserWindow: {
getAllWindows: vi.fn(() => [
{ isDestroyed: () => false, webContents: { isDestroyed: () => false, send } }
])
},
shell: { openPath: vi.fn(() => Promise.resolve('')), showItemInFolder: vi.fn() }
}
})
Expand Down
6 changes: 5 additions & 1 deletion apps/desktop/src/main/import/csv/csv-importer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import { createNoteDerivedStateProjector } from '../../projections/projectors/no
vi.mock('electron', () => {
const send = vi.fn()
return {
BrowserWindow: { getAllWindows: vi.fn(() => [{ webContents: { send } }]) },
BrowserWindow: {
getAllWindows: vi.fn(() => [
{ isDestroyed: () => false, webContents: { isDestroyed: () => false, send } }
])
},
shell: { openPath: vi.fn(() => Promise.resolve('')), showItemInFolder: vi.fn() }
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import { createNoteDerivedStateProjector } from '../../projections/projectors/no
vi.mock('electron', () => {
const send = vi.fn()
return {
BrowserWindow: { getAllWindows: vi.fn(() => [{ webContents: { send } }]) },
BrowserWindow: {
getAllWindows: vi.fn(() => [
{ isDestroyed: () => false, webContents: { isDestroyed: () => false, send } }
])
},
shell: { openPath: vi.fn(() => Promise.resolve('')), showItemInFolder: vi.fn() }
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import { createNoteDerivedStateProjector } from '../../projections/projectors/no
vi.mock('electron', () => {
const send = vi.fn()
return {
BrowserWindow: { getAllWindows: vi.fn(() => [{ webContents: { send } }]) },
BrowserWindow: {
getAllWindows: vi.fn(() => [
{ isDestroyed: () => false, webContents: { isDestroyed: () => false, send } }
])
},
shell: { openPath: vi.fn(() => Promise.resolve('')), showItemInFolder: vi.fn() }
}
})
Expand Down
6 changes: 5 additions & 1 deletion apps/desktop/src/main/import/html/html-importer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import { createNoteDerivedStateProjector } from '../../projections/projectors/no
vi.mock('electron', () => {
const send = vi.fn()
return {
BrowserWindow: { getAllWindows: vi.fn(() => [{ webContents: { send } }]) },
BrowserWindow: {
getAllWindows: vi.fn(() => [
{ isDestroyed: () => false, webContents: { isDestroyed: () => false, send } }
])
},
shell: { openPath: vi.fn(() => Promise.resolve('')), showItemInFolder: vi.fn() }
}
})
Expand Down
6 changes: 5 additions & 1 deletion apps/desktop/src/main/import/import-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'

const send = vi.fn()
vi.mock('electron', () => ({
BrowserWindow: { getAllWindows: () => [{ webContents: { send } }] }
BrowserWindow: {
getAllWindows: () => [
{ isDestroyed: () => false, webContents: { isDestroyed: () => false, send } }
]
}
}))

import { createImportContext } from './import-context'
Expand Down
6 changes: 2 additions & 4 deletions apps/desktop/src/main/import/import-context.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BrowserWindow } from 'electron'
import { ImportChannels } from '@memry/contracts/import-channels'
import { createLogger } from '../lib/logger'
import { broadcastToAllWindows } from '../lib/window-broadcast'
import type { ImportContext, ImportProgress, ImportSummary } from './types'

const logger = createLogger('Import')
Expand Down Expand Up @@ -37,9 +37,7 @@ export function createImportContext(importId: string, signal: AbortSignal): Impo
done,
summary: done ? toSummary() : undefined
}
for (const win of BrowserWindow.getAllWindows()) {
win.webContents.send(ImportChannels.events.PROGRESS, payload)
}
broadcastToAllWindows(ImportChannels.events.PROGRESS, payload)
}

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ import { createNoteDerivedStateProjector } from '../../projections/projectors/no
vi.mock('electron', () => {
const send = vi.fn()
return {
BrowserWindow: { getAllWindows: vi.fn(() => [{ webContents: { send } }]) },
BrowserWindow: {
getAllWindows: vi.fn(() => [
{ isDestroyed: () => false, webContents: { isDestroyed: () => false, send } }
])
},
shell: { openPath: vi.fn(() => Promise.resolve('')), showItemInFolder: vi.fn() }
}
})
Expand Down
6 changes: 5 additions & 1 deletion apps/desktop/src/main/import/notion/notion-importer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import { createNoteDerivedStateProjector } from '../../projections/projectors/no
vi.mock('electron', () => {
const send = vi.fn()
return {
BrowserWindow: { getAllWindows: vi.fn(() => [{ webContents: { send } }]) },
BrowserWindow: {
getAllWindows: vi.fn(() => [
{ isDestroyed: () => false, webContents: { isDestroyed: () => false, send } }
])
},
shell: { openPath: vi.fn(() => Promise.resolve('')), showItemInFolder: vi.fn() }
}
})
Expand Down
6 changes: 5 additions & 1 deletion apps/desktop/src/main/import/roam/roam-importer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import { createNoteDerivedStateProjector } from '../../projections/projectors/no
vi.mock('electron', () => {
const send = vi.fn()
return {
BrowserWindow: { getAllWindows: vi.fn(() => [{ webContents: { send } }]) },
BrowserWindow: {
getAllWindows: vi.fn(() => [
{ isDestroyed: () => false, webContents: { isDestroyed: () => false, send } }
])
},
shell: { openPath: vi.fn(() => Promise.resolve('')), showItemInFolder: vi.fn() }
}
})
Expand Down
6 changes: 4 additions & 2 deletions apps/desktop/src/main/ipc/ai-inline-handlers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const mocks = vi.hoisted(() => {
BrowserWindow: {
getAllWindows: vi.fn()
},
webContents: { send: vi.fn() },
webContents: { isDestroyed: () => false, send: vi.fn() },
startChatServer: vi.fn(),
stopChatServer: vi.fn(),
getServerPort: vi.fn(),
Expand Down Expand Up @@ -74,7 +74,9 @@ describe('AI inline IPC handlers', () => {
mocks.getSetting.mockReturnValue(
JSON.stringify({ enabled: true, provider: 'openai', apiKey: 'sk-real', model: 'gpt-4o-mini' })
)
mocks.BrowserWindow.getAllWindows.mockReturnValue([{ webContents: mocks.webContents }])
mocks.BrowserWindow.getAllWindows.mockReturnValue([
{ isDestroyed: () => false, webContents: mocks.webContents }
])
mocks.getServerPort.mockReturnValue(3434)
mocks.startChatServer.mockResolvedValue(4545)
mocks.stopChatServer.mockResolvedValue(undefined)
Expand Down
11 changes: 5 additions & 6 deletions apps/desktop/src/main/ipc/ai-inline-handlers.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { ipcMain, BrowserWindow } from 'electron'
import { ipcMain } from 'electron'
import { AIInlineChannels, AI_INLINE_SETTINGS_DEFAULTS } from '@memry/contracts/ai-inline-channels'
import type { AIInlineSettings } from '@memry/contracts/ai-inline-channels'

import { startChatServer, stopChatServer, getServerPort } from '../ai-inline/ai-chat-server'
import { getDatabase } from '../database'
import { createLogger } from '../lib/logger'
import { broadcastToAllWindows } from '../lib/window-broadcast'
import { getSetting, setSetting } from '../settings/settings-store'
import { isConnectionRefusedError, markExpectedCondition } from '../telemetry/expected-conditions'
import { withErrorHandler } from './validate'
Expand Down Expand Up @@ -61,11 +62,9 @@ export function registerAIInlineHandlers(): void {
const updated = { ...current, ...updates }
setSetting(db, SETTINGS_KEY, JSON.stringify(updated))

BrowserWindow.getAllWindows().forEach((win) => {
win.webContents.send(AIInlineChannels.events.SERVER_READY, {
key: SETTINGS_KEY,
value: maskApiKey(updated)
})
broadcastToAllWindows(AIInlineChannels.events.SERVER_READY, {
key: SETTINGS_KEY,
value: maskApiKey(updated)
})

return { success: true }
Expand Down
30 changes: 20 additions & 10 deletions apps/desktop/src/main/ipc/auth-device-handlers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,8 +339,8 @@ describe('auth-device handlers', () => {
await invokeHandler(SYNC_CHANNELS.AUTH_REQUEST_OTP, { email: 'user@example.com' })

// then clipboard polling is started - verified by advancing timers
const mockWebContents = { send: vi.fn() }
const mockWindow = { webContents: mockWebContents }
const mockWebContents = { isDestroyed: () => false, send: vi.fn() }
const mockWindow = { isDestroyed: () => false, webContents: mockWebContents }
mockGetAllWindows.mockReturnValue([mockWindow])
mockClipboardReadText.mockReturnValue('123456')

Expand Down Expand Up @@ -368,8 +368,10 @@ describe('auth-device handlers', () => {
code: '123456'
})

const mockWebContents = { send: vi.fn() }
mockGetAllWindows.mockReturnValue([{ webContents: mockWebContents }])
const mockWebContents = { isDestroyed: () => false, send: vi.fn() }
mockGetAllWindows.mockReturnValue([
{ isDestroyed: () => false, webContents: mockWebContents }
])
mockClipboardReadText.mockReturnValue('654321')

vi.advanceTimersByTime(2000)
Expand All @@ -384,8 +386,10 @@ describe('auth-device handlers', () => {
mockPostToServer.mockResolvedValue({ success: true })
await invokeHandler(SYNC_CHANNELS.AUTH_REQUEST_OTP, { email: 'user@example.com' })

const mockWebContents = { send: vi.fn() }
mockGetAllWindows.mockReturnValue([{ webContents: mockWebContents }])
const mockWebContents = { isDestroyed: () => false, send: vi.fn() }
mockGetAllWindows.mockReturnValue([
{ isDestroyed: () => false, webContents: mockWebContents }
])
mockClipboardReadText.mockReturnValue('not-a-code')

// #when
Expand All @@ -404,8 +408,10 @@ describe('auth-device handlers', () => {
// #when - advance past 10-minute timeout
vi.advanceTimersByTime(10 * 60 * 1000 + 1000)

const mockWebContents = { send: vi.fn() }
mockGetAllWindows.mockReturnValue([{ webContents: mockWebContents }])
const mockWebContents = { isDestroyed: () => false, send: vi.fn() }
mockGetAllWindows.mockReturnValue([
{ isDestroyed: () => false, webContents: mockWebContents }
])
mockClipboardReadText.mockReturnValue('123456')

vi.advanceTimersByTime(2000)
Expand Down Expand Up @@ -801,7 +807,9 @@ describe('auth-device handlers', () => {
it('removes remote devices locally without revoking the current renderer, and tolerates server 404s', async () => {
registerAuthDeviceHandlers()
const send = vi.fn()
mockGetAllWindows.mockReturnValue([{ webContents: { send } }])
mockGetAllWindows.mockReturnValue([
{ isDestroyed: () => false, webContents: { isDestroyed: () => false, send } }
])

await expect(
invokeHandler(SYNC_CHANNELS.REMOVE_DEVICE, { deviceId: 'dev-remote' })
Expand Down Expand Up @@ -837,7 +845,9 @@ describe('auth-device handlers', () => {
it('renames a device locally and broadcasts the new name', async () => {
registerAuthDeviceHandlers()
const send = vi.fn()
mockGetAllWindows.mockReturnValue([{ webContents: { send } }])
mockGetAllWindows.mockReturnValue([
{ isDestroyed: () => false, webContents: { isDestroyed: () => false, send } }
])

await expect(
invokeHandler(SYNC_CHANNELS.RENAME_DEVICE, {
Expand Down
18 changes: 7 additions & 11 deletions apps/desktop/src/main/ipc/auth-device-handlers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BrowserWindow, clipboard, ipcMain } from 'electron'
import { clipboard, ipcMain } from 'electron'

import { syncDevices } from '@memry/db-schema/schema/sync-devices'
import { KEYCHAIN_ENTRIES } from '@memry/contracts/crypto'
Expand Down Expand Up @@ -43,6 +43,7 @@ import {
} from '../sync/linking-service'
import { getValidAccessToken, retrieveToken, storeToken } from '../sync/token-manager'
import { createLogger } from '../lib/logger'
import { broadcastToAllWindows } from '../lib/window-broadcast'
import { registerCommand } from './lib/register-command'

const logger = createLogger('IPC:Sync:Device')
Expand Down Expand Up @@ -193,10 +194,7 @@ const startOtpClipboardDetection = (): void => {
lastClipboardValue = text

if (OTP_PATTERN.test(text)) {
const windows = BrowserWindow.getAllWindows()
for (const win of windows) {
win.webContents.send(SYNC_EVENTS.OTP_DETECTED, { code: text })
}
broadcastToAllWindows(SYNC_EVENTS.OTP_DETECTED, { code: text })
}
}, OTP_CLIPBOARD_POLL_MS)

Expand Down Expand Up @@ -507,12 +505,10 @@ export function registerAuthDeviceHandlers(): void {
.run()
}

for (const win of BrowserWindow.getAllWindows()) {
win.webContents.send(SYNC_EVENTS.DEVICE_RENAMED, {
deviceId: input.deviceId,
name: input.newName
})
}
broadcastToAllWindows(SYNC_EVENTS.DEVICE_RENAMED, {
deviceId: input.deviceId,
name: input.newName
})

logger.info(`Device renamed: ${input.deviceId} → ${input.newName}`)
return { success: true }
Expand Down
8 changes: 6 additions & 2 deletions apps/desktop/src/main/ipc/auth-oauth-handlers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,9 @@ describe('auth-oauth handlers', () => {
describe('AUTH_INIT_OAUTH', () => {
it('opens the provider URL and relays successful loopback callbacks', async () => {
const send = vi.fn()
mockGetAllWindows.mockReturnValue([{ webContents: { send } }])
mockGetAllWindows.mockReturnValue([
{ isDestroyed: () => false, webContents: { isDestroyed: () => false, send } }
])
registerAuthOAuthHandlers()

const result = await invokeHandler(SYNC_CHANNELS.AUTH_INIT_OAUTH, { provider: 'google' })
Expand Down Expand Up @@ -247,7 +249,9 @@ describe('auth-oauth handlers', () => {

it('relays OAuth errors and rejects malformed provider responses', async () => {
const send = vi.fn()
mockGetAllWindows.mockReturnValue([{ webContents: { send } }])
mockGetAllWindows.mockReturnValue([
{ isDestroyed: () => false, webContents: { isDestroyed: () => false, send } }
])
registerAuthOAuthHandlers()

await invokeHandler(SYNC_CHANNELS.AUTH_INIT_OAUTH, { provider: 'google' })
Expand Down
11 changes: 4 additions & 7 deletions apps/desktop/src/main/ipc/auth-oauth-handlers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BrowserWindow, ipcMain, shell } from 'electron'
import { ipcMain, shell } from 'electron'
import http from 'node:http'
import https from 'node:https'

Expand All @@ -19,6 +19,7 @@ import { startGoogleCalendarSyncRunner } from '../calendar/google/sync-service'
import { teardownSession } from '../sync/session-teardown'
import { refreshAccessToken, storeToken } from '../sync/token-manager'
import { createLogger } from '../lib/logger'
import { broadcastToAllWindows } from '../lib/window-broadcast'
import { registerCommand } from './lib/register-command'
import {
clearPendingRecoveryPhrase,
Expand Down Expand Up @@ -185,9 +186,7 @@ export function registerAuthOAuthHandlers(): void {
const cbState = reqUrl.searchParams.get('state')
if (cbState) oauthSessions.delete(cbState)

for (const win of BrowserWindow.getAllWindows()) {
win.webContents.send(SYNC_EVENTS.OAUTH_ERROR, { error: oauthError })
}
broadcastToAllWindows(SYNC_EVENTS.OAUTH_ERROR, { error: oauthError })

shutdownLoopbackServer()
return
Expand All @@ -200,9 +199,7 @@ export function registerAuthOAuthHandlers(): void {
res.end(SUCCESS_HTML)

if (code && cbState) {
for (const win of BrowserWindow.getAllWindows()) {
win.webContents.send(SYNC_EVENTS.OAUTH_CALLBACK, { code, state: cbState })
}
broadcastToAllWindows(SYNC_EVENTS.OAUTH_CALLBACK, { code, state: cbState })
}

shutdownLoopbackServer()
Expand Down
4 changes: 3 additions & 1 deletion apps/desktop/src/main/ipc/bookmarks-handlers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ vi.mock('electron', () => ({
})
},
BrowserWindow: {
getAllWindows: vi.fn(() => [{ webContents: { send: mockSend } }])
getAllWindows: vi.fn(() => [
{ isDestroyed: () => false, webContents: { isDestroyed: () => false, send: mockSend } }
])
}
}))

Expand Down
Loading
Loading