我正在尝试创建一个与本机消息传递主机chrome-token-signing进行通信的扩展。我使用金雅拓智能卡读卡器。我安装了扩展,当我退出或放入任何字符串时它返回一个cmd窗口。我有消息日志TEST: ERROR {"message":"Error when communicating with the native messaging host."}
我需要做点什么吗?
我已将主机安装在注册表中,如HKEY_LOCAL_MACHINE\software\Google\Chrome\NativeMessagingHosts\ee.ria.esteid
和值C:\Users\dev\Desktop\chrome-token-signing\host-windows\ee.ria.esteid.json
原生应用程序manifest.json:
{
"name": "ee.ria.esteid",
"description": "Give signatures with your eID on the web",
"path": "chrome-token-signing.exe",
"type": "stdio",
"allowed_origins": [
"chrome-extension://ckjefchnfjhjfedoccjbhjpbncimppeg/"
]
}
扩展名的manifest.json {
"name": "Token signing",
"version": "0.0.24",
"minimum_chrome_version": "40.0",
"manifest_version": 2,
"description": "Use your eID smart card on the web",
"icons": {
"48": "icon48.png",
"128": "icon128.png"
},
"content_scripts": [{
"matches": ["*://*/*", "file:///*"],
"exclude_matches": ["*://www.overdrive.com/*"],
"js": ["content.js"],
"run_at": "document_end",
"all_frames": true
}],
"background": {
"scripts": ["background.js"]
},
"permissions": ["nativeMessaging"],
"applications": {
"gecko": {
"id": "{443830f0-1fff-4f9a-aa1e-444bafbc7319}"
}
}
}
background.js
var NO_NATIVE_URL = "https://open-eid.github.io/chrome-token- signing/missing.html";
var HELLO_URL = "https://open-eid.github.io/chrome-token-signing/hello.html";
var DEVELOPER_URL = "https://github.com/open-eid/chrome-token- signing/wiki/DeveloperTips";
var NATIVE_HOST = "ee.ria.esteid";
var K_SRC = "src";
var K_ORIGIN = "origin";
var K_NONCE = "nonce";
var K_RESULT = "result";
var K_TAB = "tab";
var K_EXTENSION = "extension";
// Stores the longrunning ports per tab
// Used to route all request from a tab to the same host instance
var ports = {};
// Probed to false if host component is OK.
var missing = true;
console.log("Background page activated");
// XXX: probe test, because connectNative() does not allow to check the presence
// of native component for some reason
typeof chrome.runtime.onStartup !== 'undefined' && chrome.runtime.onStartup.addListener(function() {
// Also probed for in onInstalled()
_testNativeComponent().then(function(result) {
if (result === "ok") {
missing = false;
}
});
});
// Force kill of native process
// Becasue Port.disconnect() does not work
function _killPort(tab) {
if (tab in ports) {
console.log("KILL " + tab);
// Force killing with an empty message
ports[tab].postMessage({});
}
}
// Check if native implementation is OK resolves with "ok", "missing" or "forbidden"
function _testNativeComponent() {
return new Promise(function(resolve, reject) {
chrome.runtime.sendNativeMessage(NATIVE_HOST, {}, function(response) {
if (!response) {
console.log("TEST: ERROR " + JSON.stringify(chrome.runtime.lastError));
// Try to be smart and do some string matching
var permissions = "Access to the specified native messaging host is forbidden.";
var missing = "Specified native messaging host not found.";
if (chrome.runtime.lastError.message === permissions) {
resolve("forbidden")
} else if (chrome.runtime.lastError.message === missing) {
resolve("missing");
} else {
resolve("missing");
}
} else {
console.log("TEST: " + JSON.stringify(response));
if (response["result"] === "invalid_argument") {
resolve("ok");
} else {
resolve("missing"); // TODO: something better here
}
}
});
});
}
// When extension is installed, check for native component or direct to helping page
typeof chrome.runtime.onInstalled !== 'undefined' && chrome.runtime.onInstalled.addListener(function(details) {
if (details.reason === "install" || details.reason === "update") {
_testNativeComponent().then(function(result) {
var url = null;
if (result === "ok" && details.reason === "install") {
// Also set the flag, onStartup() shall be called only
// on next startup
missing = false;
// TODO: Add back HELLO page on install
// once there is a nice tutorial
url = HELLO_URL;
} else if (result === "forbidden") {
url = DEVELOPER_URL;
} else if (result === "missing"){
url = NO_NATIVE_URL;
}
if (url) {
chrome.tabs.create({'url': url + "?" + details.reason});
}
});
}
});
// When message is received from page send it to native
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(sender.id !== chrome.runtime.id && sender.extensionId !== chrome.runtime.id) {
console.log('WARNING: Ignoring message not from our extension');
// Not our extension, do nothing
return;
}
if (sender.tab) {
// Check if page is DONE and close the native component without doing anything else
if (request["type"] === "DONE") {
console.log("DONE " + sender.tab.id);
if (sender.tab.id in ports) {
// FIXME: would want to use Port.disconnect() here
_killPort(sender.tab.id);
}
} else {
request[K_TAB] = sender.tab.id;
if (missing) {
_testNativeComponent().then(function(result) {
if (result === "ok") {
missing = false;
_forward(request);
} else {
return _fail_with (request, "no_implementation");
}
});
} else {
// TODO: Check if the URL is in allowed list or not
// Either way forward to native currently
_forward(request);
}
}
}
});
// Send the message back to the originating tab
function _reply(tab, msg) {
msg[K_SRC] = "background.js";
msg[K_EXTENSION] = chrome.runtime.getManifest().version;
chrome.tabs.sendMessage(tab, msg);
}
// Fail an incoming message if the underlying implementation is not
// present
function _fail_with(msg, result) {
var resp = {};
resp[K_NONCE] = msg[K_NONCE];
resp[K_RESULT] = result;
_reply(msg[K_TAB], resp);
}
// Forward a message to the native component
function _forward(message) {
var tabid = message[K_TAB];
console.log("SEND " + tabid + ": " + JSON.stringify(message));
// Open a port if necessary
if(!ports[tabid]) {
// For some reason there does not seem to be a way to detect missing components from longrunning ports
// So we probe before opening a new port.
console.log("OPEN " + tabid + ": " + NATIVE_HOST);
// create a new port
var port = chrome.runtime.connectNative(NATIVE_HOST);
// XXX: does not indicate anything for some reason.
if (!port) {
console.log("OPEN ERROR: " + JSON.stringify(chrome.runtime.lastError));
}
port.onMessage.addListener(function(response) {
if (response) {
console.log("RECV "+tabid+": " + JSON.stringify(response));
_reply(tabid, response);
} else {
console.log("ERROR "+tabid+": " + JSON.stringify(chrome.runtime.lastError));
_fail_with(message, "technical_error");
}
});
port.onDisconnect.addListener(function() {
console.log("QUIT " + tabid);
delete ports[tabid];
// TODO: reject all pending promises for tab, if any
});
ports[tabid] = port;
ports[tabid].postMessage(message);
} else {
// Port already open
ports[tabid].postMessage(message);
}
}
content.js
/*
* Chrome token signing extension
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
var inuse = false;
// Forward the message from page.js to background.js
window.addEventListener("message", function(event) {
// We only accept messages from ourselves
if (event.source !== window)
return;
// and forward to extension
if (event.data.src && (event.data.src === "page.js")) {
event.data["origin"] = location.origin;
chrome.runtime.sendMessage(event.data, function(response) {});
// Only add unload handler if extension has been used
if (!inuse) {
// close the native component if page unloads
window.addEventListener("beforeunload", function(event) {
chrome.runtime.sendMessage({src: 'page.js', type: 'DONE'});
}, false);
inuse = true;
}
}
}, false);
// post messages from extension to page
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
window.postMessage(request, '*');
});
// inject content of page.js to the DOM of every page
// FIXME: maybe not ?
var s = document.createElement('script');
s.type = 'text/javascript';
s.innerHTML='// Promises \n\
var _eid_promises = {}; \n\
// Turn the incoming message from extension \n\
// into pending Promise resolving \n\
window.addEventListener("message", function(event) { \n\
if(event.source !== window) return; \n\
if(event.data.src && (event.data.src === "background.js")) { \n\
console.log("Page received: "); \n\
console.log(event.data); \n\
// Get the promise \n\
if(event.data.nonce) { \n\
var p = _eid_promises[event.data.nonce]; \n\
// resolve \n\
if(event.data.result === "ok") { \n\
if(event.data.signature !== undefined) { \n\
p.resolve({hex: event.data.signature}); \n\
} else if(event.data.version !== undefined) { \n\
p.resolve(event.data.extension + "/" + event.data.version); \n\
} else if(event.data.cert !== undefined) { \n\
p.resolve({hex: event.data.cert}); \n\
} else { \n\
console.log("No idea how to handle message"); \n\
console.log(event.data); \n\
} \n\
} else { \n\
// reject \n\
p.reject(new Error(event.data.result)); \n\
} \n\
delete _eid_promises[event.data.nonce]; \n\
} else { \n\
console.log("No nonce in event msg"); \n\
} \n\
} \n\
}, false); \n\
\n\
\n\
function TokenSigning() { \n\
function nonce() { \n\
var val = ""; \n\
var hex = "abcdefghijklmnopqrstuvwxyz0123456789"; \n\
for(var i = 0; i < 16; i++) val += hex.charAt(Math.floor(Math.random() * hex.length)); \n\
return val; \n\
} \n\
\n\
function messagePromise(msg) { \n\
return new Promise(function(resolve, reject) { \n\
// amend with necessary metadata \n\
msg["nonce"] = nonce(); \n\
msg["src"] = "page.js"; \n\
// send message \n\
window.postMessage(msg, "*"); \n\
// and store promise callbacks \n\
_eid_promises[msg.nonce] = { \n\
resolve: resolve, \n\
reject: reject \n\
}; \n\
}); \n\
} \n\
this.getCertificate = function(options) { \n\
var msg = {type: "CERT", lang: options.lang}; \n\
console.log("getCertificate()"); \n\
return messagePromise(msg); \n\
}; \n\
this.sign = function(cert, hash, options) { \n\
var msg = {type: "SIGN", cert: cert.hex, hash: hash.hex, hashtype: hash.type, lang: options.lang}; \n\
console.log("sign()"); \n\
return messagePromise(msg); \n\
}; \n\
this.getVersion = function() { \n\
console.log("getVersion()"); \n\
return messagePromise({ \n\
type: "VERSION" \n\
}); \n\
}; \n\
}';
(document.head || document.documentElement).appendChild(s);
page.js
/*
* Chrome token signing extension
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
// Promises
var _eid_promises = {};
// Turn the incoming message from extension
// into pending Promise resolving
window.addEventListener("message", function(event) {
if(event.source !== window) return;
if(event.data.src && (event.data.src === "background.js")) {
console.log("Page received: ");
console.log(event.data);
// Get the promise
if(event.data.nonce) {
var p = _eid_promises[event.data.nonce];
// resolve
if(event.data.result === "ok") {
if(event.data.signature !== undefined) {
p.resolve({hex: event.data.signature});
} else if(event.data.version !== undefined) {
p.resolve(event.data.extension + "/" + event.data.version);
} else if(event.data.cert !== undefined) {
p.resolve({hex: event.data.cert});
} else {
console.log("No idea how to handle message");
console.log(event.data);
}
} else {
// reject
p.reject(new Error(event.data.result));
}
delete _eid_promises[event.data.nonce];
} else {
console.log("No nonce in event msg");
}
}
}, false);
function TokenSigning() {
function nonce() {
var val = "";
var hex = "abcdefghijklmnopqrstuvwxyz0123456789";
for(var i = 0; i < 16; i++) val += hex.charAt(Math.floor(Math.random() * hex.length));
return val;
}
function messagePromise(msg) {
return new Promise(function(resolve, reject) {
// amend with necessary metadata
msg["nonce"] = nonce();
msg["src"] = "page.js";
// send message
window.postMessage(msg, "*");
// and store promise callbacks
_eid_promises[msg.nonce] = {
resolve: resolve,
reject: reject
};
});
}
this.getCertificate = function(options) {
var msg = {type: "CERT", lang: options.lang};
console.log("getCertificate()");
return messagePromise(msg);
};
this.sign = function(cert, hash, options) {
var msg = {type: "SIGN", cert: cert.hex, hash: hash.hex, hashtype: hash.type, lang: options.lang};
console.log("sign()");
return messagePromise(msg);
};
this.getVersion = function() {
console.log("getVersion()");
return messagePromise({
type: "VERSION"
});
};
}