场景

有时候需要与其他程序进行交互时,自定义协议是一个不错的选择 -- 它能在程序为启动时启动程序然后处理其它程序的动作,而这是其它解决方案,包括 HTTP 请求、共享数据库不能比的。其实日常生活中也有现成的例子,迅雷的自定义协议下载链接、BitTorrent 协议、百度网盘启动本地客户端等等。

使用

参考: 将当前可执行文件设置为协议的默认处理程序(注册表级别)open in new window

  1. 让程序保持单例启动
  2. 设置客户端支持的协议(在 Windows 中会写入到注册表)
  3. 处理命令行参数找到其中需要的 url 信息
  4. 监听 readysecond-instance 事件

让程序保持单例启动

参考: app.requestSingleInstanceLock()open in new window 注: 仅在单例模式下才能监听 second-instance 事件

// 请求单例锁,避免打开多个 electron 实例
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
  app.quit()
  return
}

设置客户端支持的协议(在 Windows 中会写入到注册表)

参考: app.setAsDefaultProtocolClient(protocol[, path, args])open in new window

import { app } from 'electron'
import path = require('path')

/**
 * 客户端默认支持的协议
 */
export class DefaultProtocolClient {
  constructor(public readonly protocol: string) {}

  /**
   * 注册一个默认支持打开的协议
   */
  register() {
    // 开发模式下在 window 运行需要做兼容
    if (
      process.env.NODE_ENV === 'development' &&
      process.platform === 'win32'
    ) {
      // 设置 electron.exe 和 app 的路径
      app.setAsDefaultProtocolClient(this.protocol, process.execPath, [
        path.resolve(process.argv[1]),
      ])
    } else {
      app.setAsDefaultProtocolClient(this.protocol)
    }
  }

  /**
   * 从命令行参数中找到 url
   * @param argv
   */
  findUrl(argv: string[]): string | undefined {
    const regExp = new RegExp(`^${this.protocol}://`)
    return argv.find((str) => regExp.test(str))
  }
}

const defaultProtocolClient = new DefaultProtocolClient('custom-protocol')

await defaultProtocolClient.register()

处理命令行参数找到其中需要的 url 信息

添加函数 handleDefaultProtocol 从命令行参数中找到 url 然后处理它。

/**
 * 处理客户端支持的默认协议
 * @param argv
 */
async function handleDefaultProtocol(argv: string[]) {
  const url = defaultProtocolClient.findUrl(argv)
  if (!url) {
    return
  }
  await dialog.showMessageBox({
    type: 'info',
    message: 'window protocol 自定义协议打开',
    detail: ` 链接:${url}`,
  })
}

监听 readysecond-instance 事件

参考: 事件: 'second-instance'open in new window

app.addListener('second-instance', async (event, argv) => {
  await handleDefaultProtocol(argv)
})
app.addListener('ready', async () => {
  await createMainWindow()
  await handleDefaultProtocol(process.argv)
})

外部调用

既然我们自定义协议的目的是让外部程序调用,那么如何使用外部调用就很重要了。

首先检查注册表中是否已经包含它了,操作 ctrl+s => 搜索注册表 => 进入注册表 => ctrl+f 查找 custom-protocol

注册表

浏览器打开

如上图所示,可以简单在浏览器中输入 custom-protocol://test 来启动程序。

自定义协议效果

nodejs 示例

在 nodejs 中使用 npm 包 openopen in new window 可以轻易打开自定义默认链接。

import * as open from 'open'

open('custom-protocol://test')

其实本质上就是拼接命令,然后执行系统命令打开 url,参考它的实现open in new window