Skip to content
This repository was archived by the owner on Jan 19, 2021. It is now read-only.

Commit 1630db2

Browse files
authored
Add popup UI (#7)
1 parent d11828c commit 1630db2

File tree

8 files changed

+263
-3
lines changed

8 files changed

+263
-3
lines changed

src/background.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//------------------------------------- Initialisation --------------------------------------//
2+
13
"use strict";
24

35
require("chrome-extension-async");
@@ -11,6 +13,9 @@ const validSenders = [
1113
"browserpass@maximbaz.com"
1214
];
1315

16+
// instance registry
17+
var registry = {};
18+
1419
// Main entry point, invoked from browserpass. No response is expected.
1520
chrome.runtime.onMessageExternal.addListener(function(request, sender) {
1621
// reject invalid senders
@@ -68,12 +73,51 @@ chrome.runtime.onMessageExternal.addListener(function(request, sender) {
6873
console.log(`Unsupported OTP type: ${otp.type}`);
6974
}
7075

76+
// update registry
77+
registry[request.settings.tab.id] = {
78+
otp: otp,
79+
host: request.settings.host
80+
};
81+
7182
// copy to clipboard
83+
// TODO only do this automatically when option is enabled
7284
if (!request.action.match(/^copy[A-Z]*/)) {
7385
copyToClipboard(otp.generate());
7486
}
7587
});
7688

89+
// popup entry point
90+
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
91+
switch (request.action) {
92+
case "getToken":
93+
if (
94+
registry.hasOwnProperty(request.tabID) &&
95+
registry[request.tabID].host === request.host
96+
) {
97+
let otp = registry[request.tabID].otp;
98+
let response = { token: otp.generate() };
99+
if (otp.type == "totp") {
100+
// refresh after this many seconds
101+
response.refresh = otp.period - (Math.floor(Date.now() / 1000) % otp.period);
102+
response.period = otp.period;
103+
}
104+
sendResponse(response);
105+
} else {
106+
sendResponse({ token: null });
107+
}
108+
break;
109+
default:
110+
throw new Error(`Unknown action: ${request.action}`);
111+
}
112+
});
113+
114+
// clean up stale registry entries
115+
chrome.tabs.onRemoved.addListener(tabID => {
116+
delete registry[tabID];
117+
});
118+
119+
//----------------------------------- Function definitions ----------------------------------//
120+
77121
/**
78122
* Generate a TOTP code
79123
*

src/fonts/OpenSans-Regular.ttf

94.2 KB
Binary file not shown.

src/manifest-chromium.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,8 @@
1010
"persistent": true,
1111
"scripts": ["js/background.dist.js"]
1212
},
13-
"permissions": ["clipboardWrite"]
13+
"browser_action": {
14+
"default_popup": "popup/popup.html"
15+
},
16+
"permissions": ["activeTab", "clipboardWrite"]
1417
}

src/manifest-firefox.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@
99
"persistent": true,
1010
"scripts": ["js/background.dist.js"]
1111
},
12+
"browser_action": {
13+
"default_popup": "popup/popup.html"
14+
},
1215
"applications": {
1316
"gecko": {
1417
"id": "browserpass-otp@maximbaz.com",
15-
"strict_min_version": "50.0"
18+
"strict_min_version": "54.0"
1619
}
1720
},
18-
"permissions": ["clipboardWrite"]
21+
"permissions": ["activeTab", "clipboardWrite"]
1922
}

src/popup/copy.svg

Lines changed: 15 additions & 0 deletions
Loading

src/popup/popup.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
<link rel="stylesheet" type="text/css" href="../css/popup.dist.css" />
6+
<script defer src="../js/popup.dist.js"></script>
7+
</head>
8+
<body>
9+
Loading...
10+
</body>
11+
</html>

src/popup/popup.js

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
//------------------------------------- Initialisation --------------------------------------//
2+
//
3+
"use strict";
4+
5+
//require("chrome-extension-async");
6+
var m = require("mithril");
7+
var token = null;
8+
var progress = null;
9+
10+
// interface definition
11+
var display = {
12+
view: function() {
13+
var nodes = [];
14+
15+
if (token !== null) {
16+
nodes.push(
17+
m("div.outline", [
18+
m("div.token", token),
19+
m("div.copy", {
20+
onclick: function() {
21+
copyToClipboard(token);
22+
}
23+
})
24+
])
25+
);
26+
if (progress !== null) {
27+
let updateProgress = vnode => {
28+
let refresh = progress.refresh - (Date.now() - progress.begin) / 1000;
29+
vnode.dom.style.transition = "none";
30+
vnode.dom.style.width = `${(refresh / progress.period) * 100}%`;
31+
setTimeout(function() {
32+
vnode.dom.style.transition = `width linear ${progress.refresh}s`;
33+
vnode.dom.style.width = "0%";
34+
}, 100);
35+
};
36+
let progressNode = m("div.progress", {
37+
oncreate: updateProgress,
38+
onupdate: updateProgress
39+
});
40+
nodes.push(progressNode);
41+
}
42+
} else {
43+
nodes.push(m("div.info", "No token available"));
44+
}
45+
46+
return nodes;
47+
}
48+
};
49+
50+
// attach to popup
51+
m.mount(document.body, display);
52+
53+
// attach key handler
54+
document.addEventListener("keydown", e => {
55+
e.preventDefault();
56+
if (e.code == "KeyC" && e.ctrlKey && token !== null) {
57+
copyToClipboard(token);
58+
}
59+
});
60+
61+
// dispatch initial token request
62+
dispatchRequest();
63+
64+
//----------------------------------- Function definitions ----------------------------------//
65+
/**
66+
* Copy text to clipboard
67+
*
68+
* @since 3.0.0
69+
*
70+
* @param string text Text to copy
71+
* @return void
72+
*/
73+
function copyToClipboard(text) {
74+
document.addEventListener(
75+
"copy",
76+
function(e) {
77+
e.clipboardData.setData("text/plain", text);
78+
e.preventDefault();
79+
},
80+
{ once: true }
81+
);
82+
document.execCommand("copy");
83+
84+
// flash container
85+
document.querySelector(".outline").classList.add("copied");
86+
setTimeout(function() {
87+
document.querySelector(".outline").classList.remove("copied");
88+
}, 200);
89+
}
90+
91+
/**
92+
* Dispatch background token request
93+
*
94+
* @since 3.0.0
95+
*
96+
* @return void
97+
*/
98+
function dispatchRequest() {
99+
// get active tab
100+
chrome.tabs.query({ active: true, currentWindow: true }, tabs => {
101+
var request = {
102+
action: "getToken",
103+
tabID: tabs[0].id,
104+
host: new URL(tabs[0].url).hostname
105+
};
106+
// request new token
107+
chrome.runtime.sendMessage(request, response => {
108+
if (response.hasOwnProperty("refresh")) {
109+
setTimeout(dispatchRequest, response.refresh * 1000);
110+
progress = {
111+
refresh: response.refresh,
112+
period: response.period,
113+
begin: Date.now()
114+
};
115+
}
116+
updateToken(response.token);
117+
});
118+
119+
return true;
120+
});
121+
}
122+
123+
/**
124+
* Update the displayed token
125+
*
126+
* @param string newToken New token data
127+
* @since 3.0.0
128+
*/
129+
function updateToken(newToken) {
130+
token = newToken;
131+
m.redraw();
132+
}

src/popup/popup.less

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
@font-face {
2+
font-family: "Open Sans";
3+
src: url("../fonts/OpenSans-Regular.ttf");
4+
}
5+
6+
body {
7+
display: flex;
8+
flex-direction: column;
9+
font-family: "Open Sans", sans-serif;
10+
font-size: 16px;
11+
line-height: 16px;
12+
margin: 0;
13+
padding: 5px;
14+
}
15+
16+
.outline {
17+
display: flex;
18+
align-items: center;
19+
outline: 1px solid #7f7f7f;
20+
padding: 3px 4px;
21+
transition: background-color 0.5s ease-out, outline 0.5s ease-out;
22+
23+
&.copied {
24+
background-color: #d0ebff;
25+
outline: 1px solid #1c7ed6;
26+
transition: none;
27+
}
28+
}
29+
30+
.token {
31+
border: none;
32+
text-align: center;
33+
}
34+
35+
.copy {
36+
background: url("../popup/copy.svg") no-repeat;
37+
background-size: cover;
38+
cursor: pointer;
39+
height: 14px;
40+
margin-left: 4px;
41+
width: 14px;
42+
}
43+
44+
.progress {
45+
background-color: #1c7ed6;
46+
height: 3px;
47+
margin: 0 -1px;
48+
}
49+
50+
.info {
51+
white-space: nowrap;
52+
}

0 commit comments

Comments
 (0)