@@ -0,0 +1,155 @@
|
||||
const POLL_MS = 2000;
|
||||
const UNITS = ["B", "KB", "MB", "GB", "TB"];
|
||||
const STATE_LABELS = {
|
||||
off: "Off",
|
||||
connecting: "Connecting",
|
||||
online: "Online",
|
||||
backoff: "Reconnecting",
|
||||
halted: "Stopped",
|
||||
};
|
||||
|
||||
function formatBytes(value) {
|
||||
let size = Math.max(0, Number(value) || 0);
|
||||
let unit = 0;
|
||||
while (size >= 1024 && unit < UNITS.length - 1) {
|
||||
size /= 1024;
|
||||
unit += 1;
|
||||
}
|
||||
return `${unit === 0 ? size : size.toFixed(1)} ${UNITS[unit]}`;
|
||||
}
|
||||
|
||||
class Popup {
|
||||
constructor(root) {
|
||||
this.root = root;
|
||||
this.timer = null;
|
||||
this.view = null;
|
||||
this.$ = (id) => root.getElementById(id);
|
||||
}
|
||||
|
||||
async send(message) {
|
||||
const reply = await chrome.runtime.sendMessage(message);
|
||||
if (!reply) {
|
||||
throw new Error("The background worker did not answer");
|
||||
}
|
||||
if (reply.error) {
|
||||
throw new Error(reply.error);
|
||||
}
|
||||
return reply;
|
||||
}
|
||||
|
||||
showError(error) {
|
||||
this.$("error").textContent = error ? error.message || String(error) : "";
|
||||
}
|
||||
|
||||
async run(message) {
|
||||
try {
|
||||
this.render(await this.send(message));
|
||||
this.showError(null);
|
||||
} catch (error) {
|
||||
this.showError(error);
|
||||
}
|
||||
}
|
||||
|
||||
render(view) {
|
||||
this.view = view;
|
||||
const { config, state, usage } = view;
|
||||
this.$("dot").className = `dot ${state.state}`;
|
||||
this.$("state").textContent = STATE_LABELS[state.state] || state.state;
|
||||
this.$("detail").textContent = state.detail || state.lastError || "";
|
||||
this.$("label").textContent = config.label;
|
||||
this.$("disclosure-card").hidden = config.disclosed;
|
||||
const toggle = this.$("toggle");
|
||||
toggle.checked = config.enabled;
|
||||
toggle.disabled = !config.disclosed || !config.hasSecret;
|
||||
this.$("toggle-label").textContent = config.enabled ? "Sharing on" : "Sharing off";
|
||||
this.$("requests").textContent = usage.requests;
|
||||
this.$("failed").textContent = usage.failed;
|
||||
this.$("down").textContent = formatBytes(usage.bytesDown);
|
||||
this.$("up").textContent = formatBytes(usage.bytesUp);
|
||||
this.$("lifetime").textContent =
|
||||
`All time: ${usage.lifetimeRequests} requests, ${formatBytes(usage.lifetimeBytes)}`;
|
||||
this.renderSettings(config);
|
||||
this.renderConnection(config);
|
||||
}
|
||||
|
||||
renderSettings(config) {
|
||||
if (this.root.activeElement && this.$("settings").contains(this.root.activeElement)) {
|
||||
return;
|
||||
}
|
||||
this.$("bandwidth").value = config.bandwidthMb;
|
||||
this.$("concurrency").value = config.maxConcurrent;
|
||||
this.$("from").value = config.activeFrom;
|
||||
this.$("to").value = config.activeTo;
|
||||
}
|
||||
|
||||
renderConnection(config) {
|
||||
this.$("forget").hidden = !config.hasSecret;
|
||||
this.$("connect-summary").textContent = config.hasSecret ? "Connection" : "Connect to a server";
|
||||
this.$("saved").textContent = config.hasSecret
|
||||
? `Connected to ${config.serverUrl} with secret ${config.secretPrefix}`
|
||||
: "";
|
||||
if (!config.hasSecret) {
|
||||
this.$("connect-box").open = true;
|
||||
}
|
||||
}
|
||||
|
||||
async requestOrigin(serverUrl) {
|
||||
const origin = new URL(serverUrl).origin;
|
||||
const granted = await chrome.permissions.request({ origins: [origin + "/*"] });
|
||||
if (!granted) {
|
||||
throw new Error("Permission to talk to that server was not granted");
|
||||
}
|
||||
}
|
||||
|
||||
async onConnect(event) {
|
||||
event.preventDefault();
|
||||
const secretField = this.$("secret");
|
||||
try {
|
||||
await this.requestOrigin(this.$("server").value);
|
||||
this.render(
|
||||
await this.send({
|
||||
cmd: "connect",
|
||||
serverUrl: this.$("server").value,
|
||||
secret: secretField.value,
|
||||
label: this.$("device-label").value,
|
||||
}),
|
||||
);
|
||||
secretField.value = "";
|
||||
this.showError(null);
|
||||
} catch (error) {
|
||||
this.showError(error);
|
||||
}
|
||||
}
|
||||
|
||||
onSettings(event) {
|
||||
event.preventDefault();
|
||||
this.run({
|
||||
cmd: "settings",
|
||||
values: {
|
||||
bandwidthMb: Number(this.$("bandwidth").value),
|
||||
maxConcurrent: Number(this.$("concurrency").value),
|
||||
activeFrom: this.$("from").value,
|
||||
activeTo: this.$("to").value,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onForget() {
|
||||
if (this.root.defaultView.confirm("Forget this device secret on this browser?")) {
|
||||
this.run({ cmd: "forget" });
|
||||
}
|
||||
}
|
||||
|
||||
start() {
|
||||
this.$("connect").addEventListener("submit", (event) => this.onConnect(event));
|
||||
this.$("settings").addEventListener("submit", (event) => this.onSettings(event));
|
||||
this.$("forget").addEventListener("click", () => this.onForget());
|
||||
this.$("toggle").addEventListener("change", (event) =>
|
||||
this.run({ cmd: "enable", enabled: event.target.checked }),
|
||||
);
|
||||
this.run({ cmd: "status" });
|
||||
this.timer = setInterval(() => this.run({ cmd: "status" }), POLL_MS);
|
||||
}
|
||||
}
|
||||
|
||||
new Popup(document).start();
|
||||
Reference in New Issue
Block a user