通过JS在浏览器中打开本地程序

一、背景

某项目包含一个用于展示的数字大屏和多个数字孪生应用。大屏由内部团队开发并云端部署,而数字孪生应用则由第三方开发并安装在本地电脑。

现有需求是实现通过点击大屏上的特定区域,触发并打开相应的数字孪生程序。

二、方案汇总

2.1 方案一:将大屏改为本地部署,通过node进行exe调用

通过node的exec方法,可以实现打开本地应用、执行脚本、传递参数等一系列功能。例如下方是一个打开记事本的node示例:

1
2
3
4
5
6
7
exec('notepad.exe', (err, stdout, stderr) => {
if (err) {
console.error(err);
return;
}
// ...
});

官方文档对于exec的介绍官方文档对于exec的介绍

该方案优势在于低成本访问速度快:改动内容较少,只需在本地启动Node.js并运行项目,另一方面将代码部署在内网还能加快加载速度并减少云服务费用。但是开发团队位置与大屏展示地点不在一起,导致大屏内容更新会更麻烦

2.2 方案二:改造数字孪生应用,增加URL Protocol协议

类似于钉钉、QQ、百度网盘等应用,都有对应的URL Protocol来启动客户端应用,如果第三方公司愿意给数字孪生大屏加上相应的URL Protocol协议,也能实现该需求。

在网页唤起“钉钉”应用的对话框在网页唤起“钉钉”应用的对话框

2.3 方案三:修改注册表,添加自定义的URL Protocol协议

在网上搜到了一篇资料《在网页中执行本地exe程序的两种方式》,其中有提到自定义添加URL Protocol协议的方法。这个方案改动更小,只需要再注册一个协议即可,缺点是在协议中需要固定程序路径

三、确定方案

首先排除了方案一,相较于三方交付的数字孪生应用,内部自研的数据大屏改动概率会更高一些。例如有些时候会根据来访用户定制一些展示内容、新的业务板块需要放在大屏展示等等。另一个因素是展示地点缺乏技术人员,其他人去更换程序、启动项目会比较麻烦,出现问题也不好排错。

其次排除了方案二,对于委外研发的项目,每一次微小改动都涉及到付费调整的问题。即使是修改贴图logo也需要额外签订合同和付费,涉及到费用问题和定制流程都比较麻烦。

所以最后选择了方案三,由内部研发人员编写一个注册表脚本,提供给展示地点的工作人员,由他们在本地电脑上双击运行脚本添加注册项。

四、脚本及调试文件

安装和卸载脚本比较简单,这里就直接提供了示例文件,只需要修改图中红色背景部分的内容即可。

4.1 安装脚本

文件名示例:installDemoProtocol.reg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\demoprotocol]
@="demoprotocol Protocol"
"URL Protocol"=""

[HKEY_CLASSES_ROOT\demoprotocol\DefaultIcon]
@="C:\\Program Files (x86)\\demo\\XXX.eve"

[HKEY_CLASSES_ROOT\demoprotocol\shell]
@=""

[HKEY_CLASSES_ROOT\demoprotocol\shell\open]
@=""

[HKEY_CLASSES_ROOT\demoprotocol\shell\open\command]
@="\"C:\\Program Files (x86)\\demo\\XXX.eve\" "

红底部分为需要调整的内容红底部分为需要调整的内容

4.2 卸载脚本

文件名示例:uninstallDemoProtocol.reg

1
2
3
Windows Registry Editor Version 5.00

[-HKEY_CLASSES_ROOT\demoprotocol]

红底部分为需要调整的内容红底部分为需要调整的内容

4.3 网页应用

在网页端只需要访问demoprotocol://即可打开对应程序,可以用按钮或者链接形式触发打开动作。当然,肯定会有监听应用是否成功打开的需求,但这一块是没有现成API来判断的,只能用一些奇淫巧计来完成。思路是这样的:当在浏览器中触发URL Protocol协议时,浏览器会弹出对话框与用户确认,这个行为会导致页面失焦

所以可以通过判断访问demoprotocol://后的1秒内,页面是否失焦来判断应用是否被打开。当然,如果用户在访问链接的一秒内切换到了其他应用,也会被误判成打开成功,但这是小概率事件,也无法避免。

下方是一个简单的示例页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Custom Protocol Detection</title>
<script>
function detectCustomProtocol() {
let start = Date.now();
let protocolCalled = false;

// 创建一个隐藏的 iframe
const iframe = document.createElement("iframe");
iframe.style.display = "none";
document.body.appendChild(iframe);

// 在一个短暂的延迟后检查是否调用成功
setTimeout(() => {
if (!protocolCalled) {
alert("Custom protocol call failed or blocked.");
}
document.body.removeChild(iframe);
}, 1500); // 超时时间设置为1.5秒

// 尝试调用自定义协议
try {
iframe.contentWindow.location.href = "demoprotocol://";

window.addEventListener("blur", () => {
let end = Date.now();
if (end - start < 1000) {
protocolCalled = true;
}
});

} catch (e) {
alert("Custom protocol call failed or blocked.");
}
}
</script>
</head>
<body>
<button onclick="detectCustomProtocol()">Test Custom Protocol</button>
</body>
</html>

五、参考资料


评论区