forked from retoor/devplacepy
Zo doen wijdat in oost
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>DevPlace Node: what is shared</title>
|
||||
<link rel="stylesheet" href="popup.css">
|
||||
</head>
|
||||
<body class="page">
|
||||
<main>
|
||||
<h1>Before you share anything</h1>
|
||||
<p class="muted">DevPlace Node lets the community use a little of your internet connection to read public pages from sites that block data centers. Read this once. Nothing runs until you accept and switch it on.</p>
|
||||
|
||||
<section class="card">
|
||||
<h2>What passes through your browser</h2>
|
||||
<ul>
|
||||
<li>The <strong>address of the page</strong> that is requested (host and full URL), for example a public Reddit thread.</li>
|
||||
<li>The <strong>page content</strong> that comes back. It is read by this extension and sent to the DevPlace server.</li>
|
||||
<li>Your <strong>public IP address</strong> is what the destination site sees. That is the point of donating.</li>
|
||||
<li>How many requests and how many bytes you moved, counted on this device and on the server.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>What is never used</h2>
|
||||
<ul>
|
||||
<li>Your <strong>cookies, logins and accounts</strong>. Every request is sent without credentials, so you are never signed in as yourself.</li>
|
||||
<li>Your browsing history, open tabs and other sites.</li>
|
||||
<li>Any host outside the built-in list, any port other than the standard secure one, any address inside your own network.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>You stay in control</h2>
|
||||
<ul>
|
||||
<li>One on/off switch in the popup. Off means disconnected.</li>
|
||||
<li>A daily data cap, a parallel request cap and optional active hours, enforced on this device even if the server asks for more.</li>
|
||||
<li>Live counters of requests and bytes in the popup.</li>
|
||||
<li>You can forget the device secret at any time, and revoke the device on your DevPlace profile.</li>
|
||||
</ul>
|
||||
<p class="muted">Pages fetched through your connection are public pages. Do not donate from a connection whose address you cannot afford to have associated with that traffic.</p>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<div class="row">
|
||||
<button type="button" class="primary" id="accept">I understand, allow it</button>
|
||||
<button type="button" id="decline">Not now</button>
|
||||
</div>
|
||||
<p class="error" id="error" role="alert"></p>
|
||||
<p class="muted" id="done"></p>
|
||||
</section>
|
||||
</main>
|
||||
<script type="module" src="disclosure.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,31 @@
|
||||
class Disclosure {
|
||||
constructor(root) {
|
||||
this.root = root;
|
||||
this.$ = (id) => root.getElementById(id);
|
||||
}
|
||||
|
||||
async accept() {
|
||||
try {
|
||||
const reply = await chrome.runtime.sendMessage({ cmd: "accept" });
|
||||
if (!reply || reply.error) {
|
||||
throw new Error(reply ? reply.error : "The background worker did not answer");
|
||||
}
|
||||
this.$("error").textContent = "";
|
||||
this.$("done").textContent =
|
||||
"Accepted. Open the extension popup, connect your device secret and switch sharing on.";
|
||||
} catch (error) {
|
||||
this.$("error").textContent = error.message;
|
||||
}
|
||||
}
|
||||
|
||||
decline() {
|
||||
this.$("done").textContent = "Nothing was enabled. You can close this tab.";
|
||||
}
|
||||
|
||||
start() {
|
||||
this.$("accept").addEventListener("click", () => this.accept());
|
||||
this.$("decline").addEventListener("click", () => this.decline());
|
||||
}
|
||||
}
|
||||
|
||||
new Disclosure(document).start();
|
||||
@@ -0,0 +1,226 @@
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
--bg: #f6f7f9;
|
||||
--card: #ffffff;
|
||||
--text: #1c2128;
|
||||
--muted: #5b6672;
|
||||
--border: #d5dae0;
|
||||
--accent: #2f6fed;
|
||||
--accent-text: #ffffff;
|
||||
--ok: #1a8a4a;
|
||||
--warn: #b7791f;
|
||||
--bad: #c0392b;
|
||||
--radius: 8px;
|
||||
--gap: 12px;
|
||||
--font: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg: #14181d;
|
||||
--card: #1d232a;
|
||||
--text: #e8ecf0;
|
||||
--muted: #97a3b0;
|
||||
--border: #323b45;
|
||||
--accent: #5b92ff;
|
||||
--accent-text: #0b1220;
|
||||
--ok: #4cc27f;
|
||||
--warn: #e0b04a;
|
||||
--bad: #ee7b6e;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font: 14px/1.45 var(--font);
|
||||
width: 100%;
|
||||
min-width: 260px;
|
||||
max-width: 420px;
|
||||
}
|
||||
|
||||
body.page {
|
||||
max-width: 640px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: var(--gap);
|
||||
display: grid;
|
||||
gap: var(--gap);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.15rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 0.95rem;
|
||||
margin: 0 0 6px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: var(--gap);
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: var(--muted);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: var(--muted);
|
||||
}
|
||||
|
||||
.dot.online {
|
||||
background: var(--ok);
|
||||
}
|
||||
|
||||
.dot.connecting,
|
||||
.dot.backoff {
|
||||
background: var(--warn);
|
||||
}
|
||||
|
||||
.dot.halted {
|
||||
background: var(--bad);
|
||||
}
|
||||
|
||||
.counters {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.counter {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.counter strong {
|
||||
display: block;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
label {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
font: inherit;
|
||||
color: var(--text);
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 7px 9px;
|
||||
}
|
||||
|
||||
button,
|
||||
.button {
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
color: var(--text);
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 7px 12px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button.primary {
|
||||
background: var(--accent);
|
||||
color: var(--accent-text);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
button.danger {
|
||||
color: var(--bad);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.switch {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.notice {
|
||||
border-left: 3px solid var(--warn);
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--bad);
|
||||
min-height: 1.2em;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.pair {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
details > summary {
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0 0 8px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>DevPlace Node</title>
|
||||
<link rel="stylesheet" href="popup.css">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="row">
|
||||
<h1>DevPlace Node</h1>
|
||||
<span class="status"><span class="dot" id="dot"></span><span id="state">off</span></span>
|
||||
</div>
|
||||
|
||||
<section class="card" id="disclosure-card" hidden>
|
||||
<p class="notice">Before this can run you must read what is shared.</p>
|
||||
<a class="button primary" id="open-disclosure" href="disclosure.html" target="_blank" rel="noopener">Read the disclosure</a>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<div class="row">
|
||||
<label class="switch"><input type="checkbox" id="toggle"> <span id="toggle-label">Sharing off</span></label>
|
||||
<span class="muted" id="label"></span>
|
||||
</div>
|
||||
<p class="muted" id="detail"></p>
|
||||
<p class="error" id="error" role="alert"></p>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<h2>Today</h2>
|
||||
<div class="counters">
|
||||
<div class="counter"><span class="muted">Requests</span><strong id="requests">0</strong></div>
|
||||
<div class="counter"><span class="muted">Downloaded</span><strong id="down">0 B</strong></div>
|
||||
<div class="counter"><span class="muted">Uploaded</span><strong id="up">0 B</strong></div>
|
||||
<div class="counter"><span class="muted">Failed</span><strong id="failed">0</strong></div>
|
||||
</div>
|
||||
<p class="muted" id="lifetime"></p>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<details id="settings-box">
|
||||
<summary>Limits on this device</summary>
|
||||
<form id="settings">
|
||||
<div class="pair">
|
||||
<label>Data per day (MB)<input type="number" id="bandwidth" min="1" step="1"></label>
|
||||
<label>Parallel requests<input type="number" id="concurrency" min="1" step="1"></label>
|
||||
</div>
|
||||
<div class="pair">
|
||||
<label>Active from<input type="time" id="from"></label>
|
||||
<label>Active to<input type="time" id="to"></label>
|
||||
</div>
|
||||
<p class="muted">Leave the hours empty to share at any time. These limits are enforced here, whatever the server asks.</p>
|
||||
<button type="submit">Save limits</button>
|
||||
</form>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<details id="connect-box">
|
||||
<summary id="connect-summary">Connect to a server</summary>
|
||||
<form id="connect">
|
||||
<label>Server URL<input type="url" id="server" placeholder="https://devplace.net" required></label>
|
||||
<label>Device secret<input type="password" id="secret" placeholder="dpn_..." autocomplete="off" required></label>
|
||||
<label>Device label<input type="text" id="device-label" maxlength="64"></label>
|
||||
<button type="submit" class="primary">Save and connect</button>
|
||||
</form>
|
||||
<p class="muted" id="saved"></p>
|
||||
<button type="button" class="danger" id="forget" hidden>Forget this device</button>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
<p class="muted"><a href="disclosure.html" target="_blank" rel="noopener">What is shared</a></p>
|
||||
</main>
|
||||
<script type="module" src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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