chore: upgrade Electron to 44.5.1 and adapt clipboard APIs

This commit is contained in:
mesalogo
2026-10-02 17:53:42 +08:00
parent 90ea33ef5c
commit d773f5e961
8 changed files with 62 additions and 19 deletions
@@ -148,7 +148,7 @@ async function main() {
[ [
'appId: live.digiman.goodbuddy.harness-smoke', 'appId: live.digiman.goodbuddy.harness-smoke',
'productName: GoodBuddyHarnessSmoke', 'productName: GoodBuddyHarnessSmoke',
'electronVersion: "43.2.0"', `electronVersion: "${require('electron/package.json').version}"`,
'asar: true', 'asar: true',
'npmRebuild: false', 'npmRebuild: false',
'files:', 'files:',
+1 -1
View File
@@ -10,7 +10,7 @@ Debian `loong64` 桌面,不代表 GoodBuddy 已将龙芯纳入正式发布支
均为 LoongArch ELF。 均为 LoongArch ELF。
预览版使用社区维护的 Electron 42.3.0 LoongArch 构建。GoodBuddy 正式版本 预览版使用社区维护的 Electron 42.3.0 LoongArch 构建。GoodBuddy 正式版本
当前使用 Electron 43.2.0,因此该预览包只用于移植和真机验证,不能作为正式 当前使用 Electron 44.5.1,因此该预览包只用于移植和真机验证,不能作为正式
发布基线。Electron 官方目前也不提供 Linux LoongArch 预编译包。 发布基线。Electron 官方目前也不提供 Linux LoongArch 预编译包。
已完成的实际构建结果: 已完成的实际构建结果:
+4 -4
View File
@@ -84,7 +84,7 @@
"@xterm/addon-search": "0.16.0", "@xterm/addon-search": "0.16.0",
"@xterm/addon-webgl": "0.19.0", "@xterm/addon-webgl": "0.19.0",
"@xterm/xterm": "6.0.0", "@xterm/xterm": "6.0.0",
"electron": "^43.2.0", "electron": "^44.5.1",
"electron-builder": "^26.15.3", "electron-builder": "^26.15.3",
"electron-vite": "^5.0.0", "electron-vite": "^5.0.0",
"esbuild": "0.25.12", "esbuild": "0.25.12",
@@ -14755,9 +14755,9 @@
} }
}, },
"node_modules/electron": { "node_modules/electron": {
"version": "43.2.0", "version": "44.5.1",
"resolved": "https://registry.npmjs.org/electron/-/electron-43.2.0.tgz", "resolved": "https://registry.npmjs.org/electron/-/electron-44.5.1.tgz",
"integrity": "sha512-80zvrgG7ZRXD+tD0IyLvrnN9n+veSxadMRsMaC9wKKP3iUbtC7rGM8+dVuCmOb0Rrwwv8ESW4awnUZh9Hbp1fA==", "integrity": "sha512-lx7AYoFIiyvEvgvpyWO9kOC60xtNPpiPZAth6Fmg9NutzpCAMLwpUlaBY2EXfZyIgUdzEmLMj9KvK0Vrokl1yQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
+1 -1
View File
@@ -385,7 +385,7 @@
"@xterm/addon-search": "0.16.0", "@xterm/addon-search": "0.16.0",
"@xterm/addon-webgl": "0.19.0", "@xterm/addon-webgl": "0.19.0",
"@xterm/xterm": "6.0.0", "@xterm/xterm": "6.0.0",
"electron": "^43.2.0", "electron": "^44.5.1",
"electron-builder": "^26.15.3", "electron-builder": "^26.15.3",
"electron-vite": "^5.0.0", "electron-vite": "^5.0.0",
"esbuild": "0.25.12", "esbuild": "0.25.12",
+40 -1
View File
@@ -4,13 +4,16 @@ import { basename, join } from 'node:path'
import { strToU8, zipSync } from 'fflate' import { strToU8, zipSync } from 'fflate'
import { afterEach, describe, expect, it, vi } from 'vitest' import { afterEach, describe, expect, it, vi } from 'vitest'
const { createFromBuffer, getSources, showOpenDialog } = vi.hoisted(() => ({ const { createFromBuffer, getSources, showOpenDialog, readText, readClipboard } = vi.hoisted(() => ({
readText: vi.fn(),
readClipboard: vi.fn(),
createFromBuffer: vi.fn(), createFromBuffer: vi.fn(),
getSources: vi.fn(), getSources: vi.fn(),
showOpenDialog: vi.fn() showOpenDialog: vi.fn()
})) }))
vi.mock('electron', () => ({ vi.mock('electron', () => ({
clipboard: { readText, read: readClipboard },
desktopCapturer: { desktopCapturer: {
getSources getSources
}, },
@@ -31,6 +34,8 @@ import { defaultDocumentParsingSettings } from './document-parsing-settings-stor
const temporaryDirectories: string[] = [] const temporaryDirectories: string[] = []
afterEach(async () => { afterEach(async () => {
readText.mockReset()
readClipboard.mockReset()
getSources.mockReset() getSources.mockReset()
showOpenDialog.mockReset() showOpenDialog.mockReset()
createFromBuffer.mockReset() createFromBuffer.mockReset()
@@ -42,6 +47,40 @@ afterEach(async () => {
}) })
describe('ContextManager', () => { describe('ContextManager', () => {
it('awaits clipboard text and prefers it over images', async () => {
readText.mockResolvedValue(' clipboard text ')
const manager = new ContextManager()
const attachment = await manager.readClipboard()
expect(attachment.kind).toBe('text')
expect(attachment.preview).toContain('clipboard text')
expect(readClipboard).not.toHaveBeenCalled()
})
it('decodes an asynchronous clipboard PNG when text is empty', async () => {
readText.mockResolvedValue('')
const data = Buffer.from([0x89, 0x50, 0x4e, 0x47])
const getType = vi.fn().mockResolvedValue(new Blob([data]))
readClipboard.mockResolvedValue([{ types: ['image/png'], getType }])
const image = {
isEmpty: () => false,
getSize: () => ({ width: 640, height: 480 }),
resize: vi.fn(),
toJPEG: () => Buffer.from([0xff, 0xd8, 0xff, 0xd9])
}
image.resize.mockReturnValue(image)
createFromBuffer.mockReturnValue(image)
const attachment = await new ContextManager().readClipboard()
expect(getType).toHaveBeenCalledWith('image/png')
expect(createFromBuffer).toHaveBeenCalledWith(data)
expect(attachment.kind).toBe('image')
})
it('rejects a clipboard without text or a supported image', async () => {
readText.mockResolvedValue('')
readClipboard.mockResolvedValue([{ types: ['text/html'] }])
await expect(new ContextManager().readClipboard()).rejects.toThrow('剪贴板中没有可用的文本或图片')
})
it('preserves logical attachment identity and frozen message bytes across a draft reparse', async () => { it('preserves logical attachment identity and frozen message bytes across a draft reparse', async () => {
const directory = await mkdtemp(join(tmpdir(), 'goodbuddy-reparse-')) const directory = await mkdtemp(join(tmpdir(), 'goodbuddy-reparse-'))
temporaryDirectories.push(directory) temporaryDirectories.push(directory)
+7 -3
View File
@@ -568,15 +568,19 @@ export class ContextManager {
) )
} }
readClipboard(): ContextAttachment { async readClipboard(): Promise<ContextAttachment> {
const text = clipboard.readText().trim() const text = (await clipboard.readText()).trim()
if (text) { if (text) {
return this.storeText('剪贴板文本.txt', text) return this.storeText('剪贴板文本.txt', text)
} }
const image = clipboard.readImage() for (const item of await clipboard.read()) {
if (!item.types.includes('image/png')) continue
const blob = await item.getType('image/png')
const image = nativeImage.createFromBuffer(Buffer.from(await blob.arrayBuffer()))
if (!image.isEmpty()) { if (!image.isEmpty()) {
return this.storeImage('剪贴板图片.png', image) return this.storeImage('剪贴板图片.png', image)
} }
}
throw new Error('剪贴板中没有可用的文本或图片') throw new Error('剪贴板中没有可用的文本或图片')
} }
+4 -4
View File
@@ -264,9 +264,9 @@ describe('terminal IPC boundary', () => {
snapshot.sessionId snapshot.sessionId
) )
expect( await expect(
electronMocks.handlers.get(ipcChannels.clipboardReadText)?.(event) electronMocks.handlers.get(ipcChannels.clipboardReadText)?.(event)
).toBe('Get-Location\r') ).resolves.toBe('Get-Location\r')
expect(electronMocks.readClipboardText).toHaveBeenCalledOnce() expect(electronMocks.readClipboardText).toHaveBeenCalledOnce()
electronMocks.handlers.get(ipcChannels.clipboardWriteText)?.( electronMocks.handlers.get(ipcChannels.clipboardWriteText)?.(
@@ -353,8 +353,8 @@ const electronMocks = vi.hoisted(() => {
openPath: vi.fn(async () => ''), openPath: vi.fn(async () => ''),
showItemInFolder: vi.fn(), showItemInFolder: vi.fn(),
openExternal: vi.fn(async () => undefined), openExternal: vi.fn(async () => undefined),
readClipboardText: vi.fn(() => 'Get-Location\r'), readClipboardText: vi.fn(async () => 'Get-Location\r'),
writeClipboardText: vi.fn() writeClipboardText: vi.fn(async () => {})
} }
}) })
+2 -2
View File
@@ -10,12 +10,12 @@ export function registerClipboardIpcHandlers(
): void { ): void {
registerHandler(ipcChannels.clipboardReadText, (event) => { registerHandler(ipcChannels.clipboardReadText, (event) => {
assertTrustedSender(event, window) assertTrustedSender(event, window)
return clipboardTextSchema.parse(clipboard.readText()) return clipboard.readText().then((text) => clipboardTextSchema.parse(text))
}) })
registerHandler(ipcChannels.clipboardWriteText, (event, input) => { registerHandler(ipcChannels.clipboardWriteText, (event, input) => {
assertTrustedSender(event, window) assertTrustedSender(event, window)
clipboard.writeText(clipboardTextSchema.parse(input)) return clipboard.writeText(clipboardTextSchema.parse(input))
}) })
} }