@pmate/cli 0.35.11 → 0.35.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,7 @@
1
+ const { createUpdateChecker } = require('./updates.cjs')
1
2
  const { app, BrowserWindow, session, shell, Menu, desktopCapturer, ipcMain, Notification, dialog } = require('electron')
2
3
  const path = require('node:path')
4
+ const { installNotificationSettings } = require('./notification-settings.cjs')
3
5
  const { createResourceHandler } = require('./resources.cjs')
4
6
  const { installMediaPermissions } = require('./media.cjs')
5
7
  const { createDesktopNotifications } = require('./notifications.cjs')
@@ -30,7 +32,7 @@ function restrict(window) {
30
32
  window.webContents.on('did-create-window', child => restrict(child))
31
33
  }
32
34
  function createWindow() {
33
- mainWindow = new BrowserWindow({ width: 1180, height: 820, minWidth: 380, minHeight: 540, title: config.productName, backgroundColor: '#f6f8fb', webPreferences: { preload: path.join(__dirname, 'preload.cjs'), partition: 'persist:desktop', nodeIntegration: false, contextIsolation: true, sandbox: true, webSecurity: true } })
35
+ mainWindow = new BrowserWindow({ width: 1180, height: 820, minWidth: 380, minHeight: 540, title: config.productName, backgroundColor: '#f6f8fb', webPreferences: { additionalArguments: [`--pmate-app-version=${app.getVersion()}`], preload: path.join(__dirname, 'preload.cjs'), partition: 'persist:desktop', nodeIntegration: false, contextIsolation: true, sandbox: true, webSecurity: true } })
34
36
  restrict(mainWindow)
35
37
  mainWindow.webContents.on('did-start-navigation', (_event, _url, _inPlace, mainFrame) => { if (mainFrame) desktopNotifications?.clearContext() })
36
38
  mainWindow.webContents.on('did-fail-load', (_event, code, _description, url, mainFrame) => {
@@ -58,9 +60,11 @@ else {
58
60
  if (process.platform === 'darwin') { void shell.openExternal('x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture'); return true }
59
61
  return false
60
62
  })
63
+ installNotificationSettings({ ipcMain, shell, origin: config.origin, getWindow: () => mainWindow })
64
+ const checkForUpdates = createUpdateChecker({ app, dialog, shell, appId: config.app })
61
65
  Menu.setApplicationMenu(Menu.buildFromTemplate([
62
66
  { role: 'appMenu' }, { role: 'editMenu' }, { role: 'viewMenu' }, { role: 'windowMenu' },
63
- { label: config.productName, submenu: [{ label: 'Back to app', click: () => { if (!mainWindow) createWindow(); else void mainWindow.loadURL(config.origin) } }] },
67
+ { label: config.productName, submenu: [{ label: '检查更新…', click: checkForUpdates }, { type: 'separator' }, { label: 'Back to app', click: () => { if (!mainWindow) createWindow(); else void mainWindow.loadURL(config.origin) } }] },
64
68
  ]))
65
69
  desktopNotifications = createDesktopNotifications({ ipcMain, Notification, origin: config.origin, appId: config.app, getWindow: () => mainWindow, showWindow: () => {
66
70
  if (!mainWindow) createWindow()
@@ -0,0 +1,12 @@
1
+ function installNotificationSettings({ ipcMain, shell, origin, getWindow, platform = process.platform }) {
2
+ ipcMain.handle('pmate:notification:settings', async event => {
3
+ const window = getWindow()
4
+ if (!window || event.sender !== window.webContents || event.senderFrame !== window.webContents.mainFrame || new URL(event.senderFrame.url).origin !== origin) throw new Error('Untrusted settings sender')
5
+ const url = platform === 'darwin' ? 'x-apple.systempreferences:com.apple.Notifications-Settings.extension'
6
+ : platform === 'win32' ? 'ms-settings:notifications' : undefined
7
+ if (!url) return false
8
+ await shell.openExternal(url)
9
+ return true
10
+ })
11
+ }
12
+ module.exports = { installNotificationSettings }
@@ -1,5 +1,6 @@
1
1
  const { contextBridge, ipcRenderer } = require('electron')
2
2
  contextBridge.exposeInMainWorld('pmateDesktop', Object.freeze({
3
+ runtime: Object.freeze({ platform: process.platform, appVersion: process.argv.find(value => value.startsWith('--pmate-app-version='))?.slice('--pmate-app-version='.length), electronVersion: process.versions.electron }),
3
4
  openScreenSettings: () => ipcRenderer.invoke('pmate:screen-settings'),
4
5
  reach: Object.freeze({
5
6
  onEvent: handler => {
@@ -20,6 +21,7 @@ contextBridge.exposeInMainWorld('pmateDesktop', Object.freeze({
20
21
  setPeople: people => ipcRenderer.invoke('pmate:call:people', people),
21
22
  }),
22
23
  notifications: Object.freeze({
24
+ openSettings: () => ipcRenderer.invoke('pmate:notification:settings'),
23
25
  present: input => ipcRenderer.invoke('pmate:notification:present', input),
24
26
  setActiveThread: threadId => ipcRenderer.invoke('pmate:notification:context', threadId),
25
27
  onOpen: handler => {
@@ -0,0 +1,51 @@
1
+ const API = 'https://apps-api.pmate.chat'
2
+ const CDN = 'https://parrot-static.pmate.chat'
3
+ const stable = value => typeof value === 'string' && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value)
4
+ function compareVersions(a, b) {
5
+ if (!stable(a) || !stable(b)) throw new Error('Invalid stable version')
6
+ const left = a.split('.').map(BigInt), right = b.split('.').map(BigInt)
7
+ for (let i = 0; i < 3; i++) if (left[i] !== right[i]) return left[i] > right[i] ? 1 : -1
8
+ return 0
9
+ }
10
+ async function findUpdate({ appId, currentVersion, platform, arch, fetchImpl = fetch }) {
11
+ const target = platform === 'darwin' ? 'mac' : platform === 'win32' ? 'win' : null
12
+ if (!target || !['arm64', 'x64'].includes(arch)) throw new Error('Unsupported update target')
13
+ const read = async url => {
14
+ const response = await fetchImpl(url, { signal: AbortSignal.timeout(15000), redirect: 'error', cache: 'no-store' })
15
+ if (!response.ok) throw new Error(`Update request failed (${response.status})`)
16
+ return response.json()
17
+ }
18
+ const envelope = await read(`${API}/versions/${encodeURIComponent(appId)}?platform=${target}&arch=${arch}`)
19
+ const release = envelope.data
20
+ if (envelope.success !== true || !release || release.app !== appId || release.platform !== target || release.arch !== arch || !stable(release.version)) throw new Error('Invalid version response')
21
+ if (compareVersions(release.version, currentVersion) <= 0) return null
22
+ const prefix = `${CDN}/desktop/${appId.split(':').map(encodeURIComponent).join('/')}/${release.version}/${target}/${arch}/`
23
+ if (release.manifestUrl !== prefix + 'release.json') throw new Error('Untrusted release manifest')
24
+ const manifest = await read(release.manifestUrl)
25
+ if (manifest.schemaVersion !== 1 || manifest.app !== appId || manifest.version !== release.version || manifest.platform !== target || manifest.arch !== arch || !Array.isArray(manifest.artifacts)) throw new Error('Release manifest mismatch')
26
+ const extension = target === 'mac' ? '.dmg' : '.exe'
27
+ const installer = manifest.artifacts.find(item => typeof item.name === 'string' && item.name.toLowerCase().endsWith(extension))
28
+ if (!installer || /[/\\]/.test(installer.name) || installer.downloadUrl !== prefix + encodeURIComponent(installer.name) || !/^[a-f0-9]{64}$/.test(installer.sha256) || !Number.isSafeInteger(installer.size) || installer.size <= 0) throw new Error('Invalid update installer')
29
+ return { version: release.version, ...installer }
30
+ }
31
+ function createUpdateChecker({ app, dialog, shell, appId, platform = process.platform, arch = process.arch, fetchImpl = fetch }) {
32
+ let checking = false
33
+ return async function checkForUpdates() {
34
+ if (checking) return
35
+ checking = true
36
+ try {
37
+ const currentVersion = app.getVersion()
38
+ const update = await findUpdate({ appId, currentVersion, platform, arch, fetchImpl })
39
+ if (!update) {
40
+ await dialog.showMessageBox({ type: 'info', title: '检查更新', message: '已是最新版本', detail: `当前版本:${currentVersion}`, buttons: ['确定'] })
41
+ return
42
+ }
43
+ const result = await dialog.showMessageBox({ type: 'info', title: '发现新版本', message: `${app.getName()} ${update.version}`, detail: `当前版本:${currentVersion}\n新版:${update.version}\n下载后打开安装包完成升级。`, buttons: ['下载新版', '稍后'], defaultId: 0, cancelId: 1 })
44
+ if (result.response === 0) await shell.openExternal(update.downloadUrl)
45
+ } catch (error) {
46
+ console.error('Desktop update check failed:', error)
47
+ await dialog.showMessageBox({ type: 'error', title: '检查更新失败', message: '无法检查更新,请稍后重试。', buttons: ['确定'] })
48
+ } finally { checking = false }
49
+ }
50
+ }
51
+ module.exports = { compareVersions, findUpdate, createUpdateChecker }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmate/cli",
3
- "version": "0.35.11",
3
+ "version": "0.35.13",
4
4
  "description": "CLI utilities for the pMate project",
5
5
  "license": "MIT",
6
6
  "bin": {