Compare commits

..

1 Commits

Author SHA1 Message Date
f9db6aad8f README.md aktualisiert 2026-03-18 21:54:26 +00:00
4 changed files with 35 additions and 369 deletions

View File

@@ -1,14 +1,39 @@
# P2P Poll App # P2P Poll App
P2P Poll - Minimal GUN-based Scaffolding There are various issues of Trust:
The possiblity to generate lots of users that do a lot of things (at a rather low cost)
The possibility to put out wrong data, maby not even contradicting but additional to existing data.
The possibility to do all kinds of shenenigans like spam other users with some requests
What this is Due to low programming knowledge, the starting point of this proposal was to mirror how normal groups of people solve issues of trust to then automate and possibly improve the process. There are already some systems out there like Trust flow or random walk. As far as i understand it, the Flexible Trust Web also already does something like this, also maby RWOT and GNUweb but i didn't read into them too much since i discovered them rather late and want to look for feedback anyway. After all, a system with a clearer consensus might be preferable to some.
- A tiny peer-to-peer polling app implemented with vanilla JS and GUN (a decentralized, CRDT-friendly database).
- No central server for poll data. Peers connect and synchronize the poll in real-time.
Notes If random new people should be able to use the system as equals to previous users, but the system never has real identities as an input, then there is no way to fully prevent the creation of new users to manipulate or sabotage the poll. But it can be assumed, that your friends are rather trustworthy and most likely also their friends and so on. And if someone makes huge ammounts or just one second account, they will probably only have the creator or maby some other people as friends, and even they might already be less socially connected than a normal user.
- This scaffold uses GUNs browser-based peer-to-peer data replication. So the social distance to another user should be evaluated to see, whether you should count their vote.
- You can extend with: This is evaluated for and by every user individually, based on the information they were sent. The ammount of contacts you won't count are displayed to you, such that you get a hint at how many people you are missing but also how many people are not counting you. This encourages people to try to prove others/vise versa and make social connections to officially tie the network closer together such that the voting system works and confirms itself. It would be great, if there was some chat attached to the poll. If people want to prove their (or others) trusworhiness within this system, they are then also encouraged to have productive discussions, probably about the matter of the poll.
- Persistent storage on each peer Everyone in a poll with you is a "contact" of yours.
- Better conflict resolution UI "users" can have "friends".
- Optional signaling server for explicit peer discovery You can also manually mark users as suspicious or trustworthy or normal again.
The system for evaluating the trustworthyness of users is somehow a mix between the concepts "weighted path score" and "trust flow" with 5 steps.
That means for 5 steps starting with you, all friends and trusted people of people looked at in this step get some trust from the people we look at: 0.8 * The trust of the looked at person (if trusted) + 0.8 * The trust of the looked at person / friends the looked at person has (if friend). Then the trust of the person that received trust may maximally be 100. The Trust you have to yourself is 100.
You can also mark someone as trustworthy or untrustworthy. That is then also sent around to everyone if you want(should be the standard, but maby a user wants to just see how the trustworthyness will look like after the change).
If you receive such an information, you can make the following calculations immidiately and after every assesment of everyones trustworthyness:
If the accused is less trustworthy then the accusing person, decrease the accused trustworthyness to 0 and the accused friends and trustees trustworthyness by the trustworthyness of the accusing person.
If the trustworhyness of the accusing person is less than the trustworthyness of the accused, then reduce the trustworthyness of the accusing person to 0 and the accusing persons friends and trustees by the trustworthyness of the accused * 0,2.
If you mark someone as trustworthy:
The Trust flowing to the trusted person from you will also be 0.8 of your trust.
Maby this should also be the effect of beeing "friends" since "trust" might be something you could more intuitively casually deal out after a short chat. If that change were to occur, then the effect would have to be switched around.
All contacts can maximally have the Trust 100.
Future matters:
If there can be any discrepancy of sent information, depending on what sender you trust most, you will mark one of the senders as untrustworthy and neglect all future information from this user. Since everything can be signed and such, that shouldnˋt be an issue tho, but if it was, the ammount of "useless" messages to already informed people might have to increase to validate received data.
A system to showcase the social connections in a 2D - format would be neat.
(most likely something like this exists already)
Obviously the user would also have to see other context like the total of all votes (trusted or not)
Anonymous polls:
A system of individually assigned trust poses a challenge for a system where you can decide not to trust some voters.
If there is no other option some compromises might be makable, such as:
-Your Friends can know what you voted for
-The Person initiating a poll just decides on the validity of participants according to an own judgement of trust at the moment of poll-creation
-A System with clear Consensus of who to trust

200
app.js
View File

@@ -1,200 +0,0 @@
// Minimal GUN-based P2P Poll scaffold (vanilla JS)
(function() {
// Initialize GUN (no server needed; can optionally connect to a relay server)
// If you want a local-only node for prototyping: Gun()
// If you want to use a signaling server for discovery, pass a list of peers to Gun().opt({peers: [...]})
const gun = Gun();
// DOM helpers
const pollSection = document.getElementById('poll');
const pollIdInput = document.getElementById('pollId');
const loadPollBtn = document.getElementById('loadPollBtn');
const createPollBtn = document.getElementById('createPollBtn');
const pollTitleInput = document.getElementById('pollTitle');
const setTitleBtn = document.getElementById('setTitleBtn');
const optionsDiv = document.getElementById('options');
const newOptionInput = document.getElementById('newOption');
const addOptionBtn = document.getElementById('addOptionBtn');
const totalVotesSpan = document.getElementById('totalVotes');
const pollIdDisplay = document.getElementById('pollIdDisplay');
let currentPollId = null;
let pollsRef = null; // Gun node for /polls/{pollId}
let pollData = null; // local cache
// Helpers
function renderOptionList(options) {
optionsDiv.innerHTML = '';
const keys = Object.keys(options || {});
if (keys.length === 0) {
optionsDiv.innerHTML = '<p>No options yet. Add one below.</p>';
return;
}
keys.forEach((optId) => {
const opt = options[optId];
const el = document.createElement('div');
el.className = 'option';
el.innerHTML = `
<span class="text">${opt.text}</span>
<button data-optid="${optId}" class="voteBtn">Vote (${opt.votes || 0})</button>
`;
optionsDiv.appendChild(el);
});
}
function updateTotalVotes() {
const opts = pollData && pollData.options ? pollData.options : {};
const total = Object.values(opts).reduce((sum, o) => sum + (o.votes || 0), 0);
totalVotesSpan.textContent = total;
}
// Load existing poll and subscribe to changes
function loadPoll(pollId) {
if (!pollId) return;
currentPollId = pollId;
pollIdDisplay.textContent = pollId;
pollsRef = gun.get('polls').get(pollId);
// Initialize structure if new poll
pollsRef.get('title').put('Untitled Poll');
pollsRef.get('options').put({}); // ensure map exists
// Real-time updates: listen to changes
pollsRef.get('title').on((title) => {
pollTitleInput.value = title || '';
});
pollsRef.on((data) => {
// data is the entire poll node if subscribed; but we explore explicit fields
});
// For options map
pollsRef.get('options').on((opts) => {
// opts is the entire options map; convert to plain object
const optionsObj = toPlainObject(opts);
pollData = {
title: null,
options: optionsObj
};
renderOptionList(optionsObj);
updateTotalVotes();
});
// Also listen for title changes separately (in case some clients push via title path)
pollsRef.get('title').on((title) => {
pollTitleInput.value = title || '';
});
// Show UI
document.getElementById('setup').style.display = 'none';
pollSection.style.display = 'block';
}
// Create a new poll
function createPoll() {
const pollId = pollIdInput.value.trim();
if (!pollId) {
alert('Please enter a poll ID to create.');
return;
}
currentPollId = pollId;
// Initialize poll
pollsRef = gun.get('polls').get(pollId);
pollsRef.put({
title: 'New Poll',
});
pollsRef.get('options').put({}); // ensure map exists
// reflect in UI
pollIdDisplay.textContent = pollId;
pollTitleInput.value = 'New Poll';
pollSection.style.display = 'block';
document.getElementById('setup').style.display = 'none';
}
// Add option
function addOption() {
if (!pollsRef) {
alert('Load or create a poll first.');
return;
}
const text = newOptionInput.value.trim();
if (!text) return;
const optionId = 'opt_' + Date.now();
pollsRef.get('options').get(optionId).put({ text, votes: 0 });
newOptionInput.value = '';
}
// Set title
function setTitle() {
if (!pollsRef) return;
const title = pollTitleInput.value.trim() || 'Untitled';
pollsRef.get('title').put(title);
}
// Vote handler (delegated)
function handleVoteClick(e) {
if (e.target.classList.contains('voteBtn')) {
const optId = e.target.getAttribute('data-optid');
if (!optId || !pollsRef) return;
const optRef = pollsRef.get('options').get(optId);
// Increment votes locally
optRef.get('votes').once((v) => {
const current = v || 0;
optRef.put({ votes: current + 1 });
});
// UI will update via the on() listener on options map
}
}
// Utilities
function toPlainObject(obj) {
// Gun emits special proxy-like objects; convert to plain object
if (!obj) return {};
// If it's already a plain object, return
if (typeof obj !== 'object') return {};
// Gun events deliver object-like; attempt shallow copy
try {
const copy = JSON.parse(JSON.stringify(obj));
return copy;
} catch (e) {
// Fallback: return as-is if cannot stringify
return obj;
}
}
// Event bindings
loadPollBtn.addEventListener('click', () => {
const id = pollIdInput.value.trim();
if (!id) {
alert('Enter a Poll ID to load.');
return;
}
loadPoll(id);
});
createPollBtn.addEventListener('click', () => {
createPoll();
});
addOptionBtn.addEventListener('click', addOption);
newOptionInput.addEventListener('keydown', (ev) => {
if (ev.key === 'Enter') addOption();
});
setTitleBtn.addEventListener('click', setTitle);
// Delegate vote clicks
optionsDiv.addEventListener('click', handleVoteClick);
// Initialize: optionally auto-create a poll if URL param present
// Example: ?pollId=my-poll-123
const urlParams = new URLSearchParams(window.location.search);
const autoPoll = urlParams.get('pollId');
if (autoPoll) {
pollIdInput.value = autoPoll;
loadPoll(autoPoll);
}
})();

View File

@@ -1,55 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>P2P Poll</title>
<link rel="stylesheet" href="styles.css" />
<!-- Gun.js via CDN for quick start -->
<script src="https://unpkg.com/gun/gun.js"></script>
</head>
<body>
<div class="container">
<h1>P2P Poll</h1>
<section id="setup" class="card">
<h2>Start or Join Poll</h2>
<div class="field">
<label for="pollId">Poll ID (share with peer):</label>
<input id="pollId" type="text" placeholder="e.g. my-poll-123" />
</div>
<button id="loadPollBtn">Load Poll</button>
<button id="createPollBtn">Create New Poll</button>
<p class="note">Tip: You can run two instances and connect by using the same Poll ID.</p>
</section>
<section id="poll" class="card" style="display:none;">
<h2>Poll</h2>
<div class="field">
<label for="pollTitle">Poll Title</label>
<input id="pollTitle" type="text" placeholder="Poll Title" />
<button id="setTitleBtn">Set Title</button>
</div>
<div id="options" class="options">
<!-- dynamic options will render here -->
</div>
<div class="field">
<input id="newOption" type="text" placeholder="Add a new option" />
<button id="addOptionBtn">Add Option</button>
</div>
<div class="summary">
<p>Total votes: <span id="totalVotes">0</span></p>
</div>
<div class="share">
<p>Share Poll ID with peer: <code id="pollIdDisplay"></code></p>
</div>
</section>
</div>
<script src="app.js"></script>
</body>
</html>

View File

@@ -1,104 +0,0 @@
:root {
--bg: #0f1220;
--card: #1a1740;
--text: #e8e3ff;
--muted: #b8a7d2;
--accent: #6bd3ff;
}
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
font-family: sans-serif;
background: #0b0a14;
color: #fff;
height: 100%;
}
.container {
max-width: 700px;
margin: 40px auto;
padding: 0 16px;
}
.card {
background: #1f1740;
border-radius: 12px;
padding: 16px;
margin-bottom: 14px;
border: 1px solid #2a1950;
}
.field {
margin-bottom: 12px;
display: flex;
gap: 8px;
align-items: center;
}
label {
min-width: 180px;
}
input[type="text"] {
flex: 1;
padding: 8px;
border-radius: 6px;
border: 1px solid #5a2d8a;
background: #2a174f;
color: #fff;
}
button {
padding: 8px 12px;
border-radius: 6px;
border: none;
background: #6bd3ff;
color: #04152a;
font-weight: bold;
cursor: pointer;
}
button:hover {
opacity: 0.95;
}
.options {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 6px;
}
.option {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
background: #2a174f;
border: 1px solid #40217a;
border-radius: 6px;
}
.option .text {
flex: 1;
}
.share {
font-size: 0.9em;
color: var(--muted);
}
.note {
color: #c8b5ff;
font-size: 0.9em;
}
.total {
font-weight: bold;
}