Implement fetch (#4)
* Implement fetch & launch action * Fix scroll behavior * Remove horizontal scrollbar * Add stubs for filling username / password * Don't pre-filter for chrome:// URLs * Add inject.js
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,4 @@
|
|||||||
/chrome
|
/chrome
|
||||||
/src/node_modules
|
/src/node_modules
|
||||||
/src/js
|
/src/js
|
||||||
/src/*.js
|
|
||||||
/src/*.log
|
/src/*.log
|
||||||
!/src/*.src.js
|
|
||||||
|
3
Makefile
3
Makefile
@@ -15,7 +15,8 @@ CHROME_FILES := manifest.json \
|
|||||||
popup/*.svg
|
popup/*.svg
|
||||||
CHROME_FILES := $(wildcard $(addprefix src/,$(CHROME_FILES))) \
|
CHROME_FILES := $(wildcard $(addprefix src/,$(CHROME_FILES))) \
|
||||||
src/js/background.dist.js \
|
src/js/background.dist.js \
|
||||||
src/js/popup.dist.js
|
src/js/popup.dist.js \
|
||||||
|
src/js/inject.dist.js
|
||||||
CHROME_FILES := $(patsubst src/%,chrome/%,$(CHROME_FILES))
|
CHROME_FILES := $(patsubst src/%,chrome/%,$(CHROME_FILES))
|
||||||
|
|
||||||
.PHONY: chrome
|
.PHONY: chrome
|
||||||
|
@@ -5,7 +5,7 @@ CLEAN_FILES := js
|
|||||||
PRETTIER_FILES := $(wildcard *.js popup/*.js *.css popup/*.css)
|
PRETTIER_FILES := $(wildcard *.js popup/*.js *.css popup/*.css)
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: deps prettier js/background.dist.js js/popup.dist.js
|
all: deps prettier js/background.dist.js js/popup.dist.js js/inject.dist.js
|
||||||
|
|
||||||
.PHONY: deps
|
.PHONY: deps
|
||||||
deps:
|
deps:
|
||||||
@@ -23,6 +23,10 @@ js/popup.dist.js: $(BROWSERIFY) popup/*.js
|
|||||||
[ -d js ] || mkdir -p js
|
[ -d js ] || mkdir -p js
|
||||||
$(BROWSERIFY) -o js/popup.dist.js popup/popup.js
|
$(BROWSERIFY) -o js/popup.dist.js popup/popup.js
|
||||||
|
|
||||||
|
js/inject.dist.js: $(BROWSERIFY) inject.js
|
||||||
|
[ -d js ] || mkdir -p js
|
||||||
|
$(BROWSERIFY) -o js/inject.dist.js inject.js
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(CLEAN_FILES)
|
rm -rf $(CLEAN_FILES)
|
||||||
|
@@ -60,6 +60,12 @@ async function handleMessage(settings, message, sendResponse) {
|
|||||||
sendResponse({ status: "error", message: "Action is missing" });
|
sendResponse({ status: "error", message: "Action is missing" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fetch file & parse fields if a login entry is present
|
||||||
|
if (typeof message.login !== "undefined") {
|
||||||
|
await parseFields(settings, message.login);
|
||||||
|
}
|
||||||
|
|
||||||
|
// route action
|
||||||
switch (message.action) {
|
switch (message.action) {
|
||||||
case "getSettings":
|
case "getSettings":
|
||||||
sendResponse({
|
sendResponse({
|
||||||
@@ -76,7 +82,53 @@ async function handleMessage(settings, message, sendResponse) {
|
|||||||
var response = await hostAction(settings, "list");
|
var response = await hostAction(settings, "list");
|
||||||
sendResponse(response.data.files);
|
sendResponse(response.data.files);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
sendResponse({
|
||||||
|
status: "error",
|
||||||
|
message: "Unable to enumerate password files" + e.toString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "launch":
|
||||||
|
try {
|
||||||
|
var tab = (await browser.tabs.query({ active: true, currentWindow: true }))[0];
|
||||||
|
var url = message.login.fields.url ? message.login.fields.url : response.login.url;
|
||||||
|
if (!url.match(/:\/\//)) {
|
||||||
|
url = "http://" + url;
|
||||||
|
}
|
||||||
|
chrome.tabs.update(tab.id, { url: url });
|
||||||
|
sendResponse({ status: "ok" });
|
||||||
|
} catch (e) {
|
||||||
|
sendResponse({
|
||||||
|
status: "error",
|
||||||
|
message: "Unable to launch URL: " + e.toString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "fill":
|
||||||
|
try {
|
||||||
|
var tab = (await browser.tabs.query({ active: true, currentWindow: true }))[0];
|
||||||
|
await browser.tabs.executeScript(tab.id, { file: "js/inject.dist.js" });
|
||||||
|
// check login fields
|
||||||
|
if (message.login.fields.login === null) {
|
||||||
|
throw new Error("No login is available");
|
||||||
|
}
|
||||||
|
if (message.login.fields.secret === null) {
|
||||||
|
throw new Error("No password is available");
|
||||||
|
}
|
||||||
|
var fillFields = JSON.stringify({
|
||||||
|
login: message.login.fields.login,
|
||||||
|
secret: message.login.fields.secret
|
||||||
|
});
|
||||||
|
// fill form via injected script
|
||||||
|
await browser.tabs.executeScript(tab.id, {
|
||||||
|
code: `window.browserpass.fillLogin(${fillFields});`
|
||||||
|
});
|
||||||
|
sendResponse({ status: "ok" });
|
||||||
|
} catch (e) {
|
||||||
|
sendResponse({
|
||||||
|
status: "error",
|
||||||
|
message: "Unable to fill credentials: " + e.toString()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -110,6 +162,65 @@ function hostAction(settings, action, params = {}) {
|
|||||||
return browser.runtime.sendNativeMessage(appID, request);
|
return browser.runtime.sendNativeMessage(appID, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch file & parse fields
|
||||||
|
*
|
||||||
|
* @since 3.0.0
|
||||||
|
*
|
||||||
|
* @param object settings Settings object
|
||||||
|
* @param object login Login object
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
async function parseFields(settings, login) {
|
||||||
|
var response = await hostAction(settings, "fetch", {
|
||||||
|
store: login.store,
|
||||||
|
file: login.login + ".gpg"
|
||||||
|
});
|
||||||
|
if (response.status != "ok") {
|
||||||
|
throw new Error(JSON.stringify(response)); // TODO handle host error
|
||||||
|
}
|
||||||
|
|
||||||
|
// save raw data inside login
|
||||||
|
login.raw = response.data.contents;
|
||||||
|
|
||||||
|
// parse lines
|
||||||
|
login.fields = {
|
||||||
|
secret: ["secret", "password", "pass"],
|
||||||
|
login: ["login", "username", "user", "email"],
|
||||||
|
url: ["url", "uri", "website", "site", "link", "launch"]
|
||||||
|
};
|
||||||
|
var lines = login.raw.split(/[\r\n]+/).filter(line => line.trim().length > 0);
|
||||||
|
lines.forEach(function(line) {
|
||||||
|
// split key / value
|
||||||
|
var parts = line
|
||||||
|
.split(":", 2)
|
||||||
|
.map(value => value.trim())
|
||||||
|
.filter(value => value.length);
|
||||||
|
if (parts.length != 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// assign to fields
|
||||||
|
for (var key in login.fields) {
|
||||||
|
if (Array.isArray(login.fields[key]) && login.fields[key].indexOf(parts[0]) >= 0) {
|
||||||
|
login.fields[key] = parts[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// clean up unassigned fields
|
||||||
|
for (var key in login.fields) {
|
||||||
|
if (Array.isArray(login.fields[key])) {
|
||||||
|
if (key == "secret" && lines.length) {
|
||||||
|
login.fields.secret = lines[0];
|
||||||
|
} else {
|
||||||
|
login.fields[key] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrap inbound messages to fetch native configuration
|
* Wrap inbound messages to fetch native configuration
|
||||||
*
|
*
|
||||||
|
18
src/inject.js
Normal file
18
src/inject.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
(function() {
|
||||||
|
/**
|
||||||
|
* Fill password
|
||||||
|
*
|
||||||
|
* @since 3.0.0
|
||||||
|
*
|
||||||
|
* @param object login Login fields
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function fillLogin(login) {
|
||||||
|
alert("Fill login: " + JSON.stringify(login));
|
||||||
|
}
|
||||||
|
|
||||||
|
// set window object
|
||||||
|
window.browserpass = {
|
||||||
|
fillLogin: fillLogin
|
||||||
|
};
|
||||||
|
})();
|
@@ -23,7 +23,7 @@ function Interface(settings, logins) {
|
|||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.logins = logins;
|
this.logins = logins;
|
||||||
this.results = [];
|
this.results = [];
|
||||||
this.active = true;
|
this.active = !settings.tab.url.match(/^chrome:\/\//);
|
||||||
this.searchPart = new SearchInterface(this);
|
this.searchPart = new SearchInterface(this);
|
||||||
|
|
||||||
// initialise with empty search
|
// initialise with empty search
|
||||||
@@ -69,6 +69,7 @@ function view(ctl, params) {
|
|||||||
result.doAction("fill");
|
result.doAction("fill");
|
||||||
},
|
},
|
||||||
onkeydown: function(e) {
|
onkeydown: function(e) {
|
||||||
|
e.preventDefault();
|
||||||
switch (e.code) {
|
switch (e.code) {
|
||||||
case "ArrowDown":
|
case "ArrowDown":
|
||||||
if (e.target.nextSibling) {
|
if (e.target.nextSibling) {
|
||||||
@@ -87,6 +88,9 @@ function view(ctl, params) {
|
|||||||
case "Enter":
|
case "Enter":
|
||||||
result.doAction("fill");
|
result.doAction("fill");
|
||||||
break;
|
break;
|
||||||
|
case "KeyG":
|
||||||
|
result.doAction("launch");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -104,7 +108,7 @@ function view(ctl, params) {
|
|||||||
title: "Copy username",
|
title: "Copy username",
|
||||||
onclick: function(e) {
|
onclick: function(e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
result.doAction("copyUser");
|
result.doAction("copyUsername");
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
m("div.action.launch", {
|
m("div.action.launch", {
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
min-width: 260px;
|
min-width: 260px;
|
||||||
|
overflow-x: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ body {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
min-height: 29px;
|
min-height: 29px;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.part:last-child {
|
.part:last-child {
|
||||||
|
@@ -33,11 +33,14 @@ browser.tabs.query({ active: true, currentWindow: true }, async function(tabs) {
|
|||||||
* @since 3.0.0
|
* @since 3.0.0
|
||||||
*
|
*
|
||||||
* @param Error error Error object
|
* @param Error error Error object
|
||||||
|
* @param string type Error type
|
||||||
*/
|
*/
|
||||||
function handleError(error) {
|
function handleError(error, type = "error") {
|
||||||
console.log(error);
|
if (type == "error") {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
var errorNode = document.createElement("div");
|
var errorNode = document.createElement("div");
|
||||||
errorNode.setAttribute("class", "part error");
|
errorNode.setAttribute("class", "part " + type);
|
||||||
errorNode.textContent = error.toString();
|
errorNode.textContent = error.toString();
|
||||||
document.body.innerHTML = "";
|
document.body.innerHTML = "";
|
||||||
document.body.appendChild(errorNode);
|
document.body.appendChild(errorNode);
|
||||||
@@ -115,9 +118,31 @@ async function run(settings) {
|
|||||||
*/
|
*/
|
||||||
async function withLogin(action) {
|
async function withLogin(action) {
|
||||||
try {
|
try {
|
||||||
|
// replace popup with a "please wait" notice
|
||||||
|
switch (action) {
|
||||||
|
case "fill":
|
||||||
|
handleError("Filling login details...", "notice");
|
||||||
|
break;
|
||||||
|
case "launch":
|
||||||
|
handleError("Launching URL...", "notice");
|
||||||
|
break;
|
||||||
|
case "copyPassword":
|
||||||
|
handleError("Copying password to clipboard...", "notice");
|
||||||
|
break;
|
||||||
|
case "copyUsername":
|
||||||
|
handleError("Copying username to clipboard...", "notice");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
handleError("Please wait...", "notice");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hand off action to background script
|
||||||
var response = await browser.runtime.sendMessage({ action: action, login: this });
|
var response = await browser.runtime.sendMessage({ action: action, login: this });
|
||||||
if (response.status != "ok") {
|
if (response.status != "ok") {
|
||||||
throw new Error(response.message);
|
throw new Error(response.message);
|
||||||
|
} else {
|
||||||
|
window.close();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleError(e);
|
handleError(e);
|
||||||
|
@@ -32,17 +32,16 @@ function view(ctl, params) {
|
|||||||
return m(
|
return m(
|
||||||
"form.part.search",
|
"form.part.search",
|
||||||
{
|
{
|
||||||
onsubmit: function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
},
|
|
||||||
onkeydown: function(e) {
|
onkeydown: function(e) {
|
||||||
switch (e.code) {
|
switch (e.code) {
|
||||||
case "ArrowDown":
|
case "ArrowDown":
|
||||||
|
e.preventDefault();
|
||||||
if (self.popup.results.length) {
|
if (self.popup.results.length) {
|
||||||
document.querySelector("*[tabindex]").focus();
|
document.querySelector("*[tabindex]").focus();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "Enter":
|
case "Enter":
|
||||||
|
e.preventDefault();
|
||||||
if (self.popup.results.length) {
|
if (self.popup.results.length) {
|
||||||
self.popup.results[0].doAction("fill");
|
self.popup.results[0].doAction("fill");
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user