Zo doen wijdat in oost

This commit is contained in:
2026-10-16 07:20:44 +02:00
parent 25d9cb1c7d
commit 6f80a820da
229 changed files with 23894 additions and 74 deletions
+55
View File
@@ -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>
+31
View File
@@ -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();
+226
View File
@@ -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;
}
+77
View File
@@ -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>
+155
View File
@@ -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();