Compare commits

..

1 Commits

Author SHA1 Message Date
1910e479ec Merge final group into main (#3) 2026-05-10 20:10:01 +00:00
4 changed files with 76 additions and 4 deletions

View File

@@ -1 +1,56 @@
# P2P Poll App # Polly - P2P Poll App
A lightweight, real-time collaborative polling application that uses [Yjs](https://yjs.dev/) and [WebRTC](https://de.wikipedia.org/wiki/WebRTC) to allow multiple users to create options, vote, and see live results without a centralized database or back-end server.
### 🚀 Features
- Real-time Collaboration: Instant synchronization of poll titles, options, and votes across all connected peers using [CRDTs (Conflict-free Replicated Data Types)](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type).
- P2P Connectivity: Uses WebRTC via [y-webrtc](https://github.com/yjs/y-webrtc) for direct browser-to-browser communication.
- Dynamic Voting: * Add new options on the fly.
- Live-updating progress bars and vote tallies.
- Automatic sorting of options by vote count.
- Voting Deadline: A shared countdown timer (2 minutes) that locks the poll for all participants once expired.
- Awareness & Presence: A status bar showing connection health and the number of active peers currently in the room.
- Local Persistence: Uses [y-indexeddb](https://github.com/yjs/y-indexeddb) to save the poll state locally in your browser, ensuring data isn't lost if you refresh or lose connection.
- No Setup Required: Unique "rooms" are created via URL parameters, making it easy to share a link and start a poll instantly.
### 🛠 Tech Stack
- Language: TypeScript
- State Management: Yjs (Shared data types: Y.Doc, Y.Map, Y.Text)
- Networking: y-webrtc (WebRTC provider for Yjs)
- UI: Vanilla DOM manipulation (No heavy frameworks like React or Vue)
### 💡 How It Works
- Room Creation: When you open the app, it checks for a ?room= parameter. If none exists, it generates a unique ID and updates the URL.
- State Synchronization: The y-webrtc provider connects users with the same room ID. Any change to sync.options or sync.votes is propagated to all users.
- Local Reactivity: Components use .observe() and .observeDeep() on Yjs types to trigger a re-render of the UI whenever the shared state changes.
- Voting: Votes are stored in a Y.Map<string> where the key is the User ID and the value is the Option ID. This ensures each user can only have one active vote at a time.
You can simulate a second user by opening an incognito Tab.
### 🔧 Installation, Development and Deployment
- Install dependencies:
```npm install yjs y-webrtc y-indexeddb```
- Development:
```npm run dev```
- Deployment:
- The code currently uses an Y-Webrtc-Signaling-Server at localhost:4444 that starts with `npm run dev` for development.
- To deploy the App, you need to set up a publicly available signaling server and set the address in the `synx.ts`. E.g. with Docker using the [funnyzak/y-webrtc-signaling](https://hub.docker.com/r/funnyzak/y-webrtc-signaling) image:
```version: '3.1'
services:
y-webrtc-signaling:
container_name: y-webrtc-signaling
image: funnyzak/y-webrtc-signaling:latest
restart: always
network_mode: bridge
ports:
- "4444:4444"
dns: 8.8.8.8```

14
package-lock.json generated
View File

@@ -8,6 +8,7 @@
"name": "polly-p2p-poll", "name": "polly-p2p-poll",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"uuid": "^13.0.0",
"y-indexeddb": "^9.0.12", "y-indexeddb": "^9.0.12",
"y-webrtc": "^10.3.0", "y-webrtc": "^10.3.0",
"yjs": "^13.6.27" "yjs": "^13.6.27"
@@ -1288,6 +1289,19 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/uuid": {
"version": "13.0.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.2.tgz",
"integrity": "sha512-vzi9uRZ926x4XV73S/4qQaTwPXM2JBj6/6lI/byHH1jOpCzb0zDbfytgA9LcN/hzb2l7WQSQnxITOVx5un/wGw==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist-node/bin/uuid"
}
},
"node_modules/vite": { "node_modules/vite": {
"version": "7.3.2", "version": "7.3.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz",

View File

@@ -4,14 +4,15 @@
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "PORT=4444 npx y-webrtc & vite",
"build": "tsc --noEmit && vite build", "build": "tsc --noEmit && vite build",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"y-indexeddb": "^9.0.12", "y-indexeddb": "^9.0.12",
"y-webrtc": "^10.3.0", "y-webrtc": "^10.3.0",
"yjs": "^13.6.27" "yjs": "^13.6.27",
"uuid": "^13.0.0"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.9.2", "typescript": "^5.9.2",

View File

@@ -28,7 +28,9 @@ export function initSync(roomId: string): AppSync {
? "connecting" ? "connecting"
: "offline"; : "offline";
const provider = new WebrtcProvider(roomId, doc); const provider = new WebrtcProvider(roomId, doc,{
signaling: ["ws://localhost:4444"]
});
const persistence = new IndexeddbPersistence(roomId, doc); const persistence = new IndexeddbPersistence(roomId, doc);
const syncConnectionStatus = (status: ConnectionStatus) => { const syncConnectionStatus = (status: ConnectionStatus) => {