Appearance
浏览器插件与原生应用的通讯
- 参考示例
https://github.com/mdn/webextensions-examples/tree/main/native-messaging
- chrome 插件文档
https://developer.chrome.com/docs/extensions/reference/
关于nativeMessaging与注册表
- chrome https://developer.chrome.com/docs/apps/nativeMessaging/
- mdn https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_manifests#manifest_location
这里主要以mac/win系统chrome为主
- 原生应用的manifest
json
{
"name": "com.my_company.my_application",
"description": "My Application",
"path": "C:\\Program Files\\My Application\\chrome_native_messaging_host.exe",
"type": "stdio",
"allowed_origins": [
"chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/"
]
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
mac manifest的位置
win manifest的位置
在Windows上,清单文件可以位于文件系统中的任何位置。应用程序安装程序必须创建注册表项HKEY_LOCAL_MACHINE\SOFTWARE\Google\Chrome\NativeMessagingHosts\_com.my_company.my_application_
或HKEY_CURRENT_USER\SOFTWARE\Google\Chrome\NativeMessagingHosts\_com.my_company.my_application_
,并将该注册表项的默认值设置为清单文件的完整路径。例如,使用以下命令:
sh
REG ADD "HKCU\Software\Google\Chrome\NativeMessagingHosts\com.my_company.my_application" /ve /t REG_SZ /d "C:\path\to\nmh-manifest.json" /f
1
或使用以下.reg
文件:
sh
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Google\Chrome\NativeMessagingHosts\com.my_company.my_application]
@="C:\\path\\to\\nmh-manifest.json"
1
2
3
2
3
当 Chrome 查找本机消息传递主机时,首先查询 32 位注册表,然后查询 64 位注册表。
自我尝试步骤
权限
插件manifest.json
json
{
"name": "My extension",
...
"browser_specific_settings": {
"gecko": {
"id": "ping_pong@example.org",
"strict_min_version": "50.0",
},
},
"permissions": [
"nativeMessaging"
],
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
向原生发收消息
background.js
json
// 循环给native发送消息
const port = browser.runtime.connectNative('ping_pong')
/*
Listen for messages from the app.
*/
port.onMessage.addListener((response) => {
console.log(`Received: ${response}`)
})
setInterval(() => {
port.postMessage('ping')
}, 1000)
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
原生应用的manifest.json
json
{
"name": "ping_pong",
"description": "My Application",
"path": "/Users/tanjie/Desktop/ping_pong.app/Contents/MacOS/native-message-chrome-ext",
"type": "stdio",
"allowed_origins": [
"chrome-extension://ebnbccpndlpnbgdohobmhkfgnpfhkenl/"
]
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
path要指向到具体的可自行文件
原生app的接收 Unity c#代码
csharp
using System;
using System.IO;
using System.Text;
using System.Threading;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.UI;
public class ChromeCommunicator : MonoBehaviour
{
private Thread workThread;
private SynchronizationContext mainThreadSynContext;
public Text text;
private void Start()
{
mainThreadSynContext = SynchronizationContext.Current;
workThread = new Thread(new ThreadStart(DoWork));
workThread.Start();
}
private void DoWork()//这个是workThread线程执行的
{
//这儿做些事(连接什么的。。。)
while (OpenStandardStreamIn() != null || OpenStandardStreamIn() != "")
{
mainThreadSynContext.Post(new SendOrPostCallback(OnConnected), OpenStandardStreamIn());//通知主线程
// FIXME: 这里的输出似乎没效果
// OpenStandardStreamOut("Received to Native App: " + OpenStandardStreamIn());
// OpenStandardStreamOut("Recieved: " + OpenStandardStreamIn());
}
}
public static void OpenStandardStreamOut(string stringData)
{
string outString = "\"" + stringData + "\"";
int dataLength = outString.Length;
byte[] bytes = BitConverter.GetBytes(dataLength);
Stream stdout = Console.OpenStandardOutput();
for (int i = 0; i < 4; i++) stdout.WriteByte(bytes[i]);
Console.Write(outString);
}
static string OpenStandardStreamIn()
{
// FIXME: 指定输入输出格式不行 所以采用 DecString 方法
// Console.OutputEncoding = System.Text.Encoding.UTF8;
// Console.InputEncoding = System.Text.Encoding.UTF8;
//// We need to read first 4 bytes for length information
Stream stdin = Console.OpenStandardInput();
int length = 0;
byte[] bytes = new byte[4];
stdin.Read(bytes, 0, 4);
length = System.BitConverter.ToInt32(bytes, 0);
string input = "";
for (int i = 0; i < length; i++)
{
input += (char)stdin.ReadByte();
}
return DecString(input);
}
private void OnConnected(object state)//由于是主线程的同步对象Post调用,这个是在主线程中执行的
{
//这里就回到了主线程里面了
var json = (string)state;
Danmu dm = JsonConvert.DeserializeObject<Danmu>(json);
if (dm.data.message!="")
{
text.text = dm.data.message;
}
}
public static string DecString(string str)
{
Encoding utf8=Encoding.GetEncoding("iso-8859-1");
// Encoding utf8=Encoding.UTF8;
// Encoding utf8=Encoding.UTF32;
byte[] btArr = utf8.GetBytes(str);
return Encoding.UTF8.GetString(btArr);
}
private void OnDestroy()
{
if (workThread != null)
{
workThread.Abort();
}
}
private void OnApplicationPause(bool pauseStatus)
{
workThread?.Abort();
}
}
[Serializable]
public class Danmu
{
public string roomId;
public Data data;
}
[Serializable]
public class Data
{
public int type;
public string typeText;
public string message;
public string face;
public string uname;
public string uid;
public string fans;
public string platform;
}
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
将manifest移动到~/Library/Application Support/Google/Chrome/NativeMessagingHosts/ping-pong.json
启动浏览器插件,unity 页面展示了浏览器推送过来的弹幕
将应用程序打包成安装包dmg
参考:https://zhuanlan.zhihu.com/p/56864296
本想打包成安装程序pkg,安装的过程中直接写入注册表,还没发现比较好用的软件 所以目前的简单处理方式是第一次打开程序的用程序写入注册表
nodejs
给权限 chmod +x ./ping_pong.js
js
#!/usr/local/bin/node
(() => {
// console.log('Hello from Node.js');
let payloadSize = null;
// A queue to store the chunks as we read them from stdin.
// This queue can be flushed when `payloadSize` data has been read
let chunks = [];
// Only read the size once for each payload
const sizeHasBeenRead = () => Boolean(payloadSize);
// All the data has been read, reset everything for the next message
const flushChunksQueue = () => {
payloadSize = null;
chunks.splice(0);
};
const processData = () => {
// Create one big buffer with all the chunks
const stringData = Buffer.concat(chunks);
// The browser will emit the size as a header of the payload,
// if it hasn't been read yet, do it.
// The next time we'll need to read the payload size is when all of the data
// of the current payload has been read (i.e. data.length >= payloadSize + 4)
if (!sizeHasBeenRead()) {
payloadSize = stringData.readUInt32LE(0);
}
// If the data we have read so far is >= to the size advertised in the header,
// it means we have all of the data sent.
// We add 4 here because that's the size of the bytes that hold the payloadSize
if (stringData.length >= (payloadSize + 4)) {
// Remove the header
const contentWithoutSize = stringData.slice(4, (payloadSize + 4));
// Reset the read size and the queued chunks
flushChunksQueue();
const json = JSON.parse(contentWithoutSize);
// console.log(json);
// process.stdout.write(JSON.stringify({message:'pong'}))
// Do something with the data…
// const message = { message: 'Hello Chrome' };
// const jsonMessage = JSON.stringify(message,null,0);
// const utf8Message = jsonMessage.toString('utf8');
// console.log(utf8Message);
// process.stdout.write(utf8Message);
}
};
process.stdin.on('readable', () => {
// A temporary variable holding the nodejs.Buffer of each
// chunk of data read off stdin
let chunk = null;
// Read all of the available data
while ((chunk = process.stdin.read()) !== null) {
chunks.push(chunk);
}
processData();
});
process.stdin.on('end', () => {
// console.log('end');
const message = { message: 'Hello Chrome' };
const jsonMessage = JSON.stringify(message);
const utf8Message = jsonMessage.toString('utf8');
process.stdout.write(utf8Message);
})
process.stdin.on('error', (err) => {
// console.log('error', err);
})
})();
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
nodejs 使用第三方库
给权限 chmod +x ./ping_pong.js
确保程序文件可直接在命令行执行
js
#!/usr/local/bin/node
// Might be good to use an explicit path to node on the shebang line in case
// it isn't in PATH when launched by Chrome.
var fs = require('fs');
// var nativeMessage = require('../index');
var nativeMessage = require('chrome-native-messaging');
var input = new nativeMessage.Input();
var transform = new nativeMessage.Transform(messageHandler);
var output = new nativeMessage.Output();
process.stdin
.pipe(input)
.pipe(transform)
.pipe(output)
.pipe(process.stdout)
;
var subscriptions = {};
var timer = setInterval(function() {
if (subscriptions.time) {
output.write({ time: new Date().toISOString() });
}
}, 1000);
input.on('end', function() {
clearInterval(timer);
});
function messageHandler(msg, push, done) {
if (msg.readdir) {
fs.readdir(msg.readdir, function(err, files) {
if (err) {
push({ error: err.message || err });
} else {
files.forEach(function(file) {
push({ file: file });
});
}
done();
});
} else if (msg.subscribe) {
subscriptions[msg.subscribe] = true;
push({ subscribed: msg.subscribe });
done();
} else if (msg.unsubscribe) {
delete subscriptions[msg.unsubscribe];
push({ unsubscribed: msg.unsubscribe });
done();
} else {
// Just echo the message:
push(msg);
done();
}
}
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60