156 lines
4.6 KiB
JavaScript
156 lines
4.6 KiB
JavaScript
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();
|