Files
427e7578-d7bf-49c8-aee9-2dd…/frontend/src/lib/yjs-setup.ts
akinwumisehinde e14bb6d425 feat: implement dynamic P2P polling app with real-time synchronization
- Add complete P2P polling application with React + TypeScript frontend
- Implement Node.js backend with Yjs WebSocket and WebRTC signaling
- Support dynamic poll creation, answer management, and voting
- Add CRDT-based state synchronization using Yjs for conflict-free merging
- Implement user tracking and vote prevention (one vote per user per option)
- Create modern UI with Tailwind CSS and visual feedback
- Add comprehensive documentation and setup instructions

Features:
- Users can create polls with custom questions
- Anyone can add answer options to any poll
- Real-time voting with instant cross-client synchronization
- Smart vote tracking with visual feedback for voted options
- User attribution showing who created polls and options
- Connection status indicators for WebSocket and P2P connections

Technical:
- Hybrid P2P architecture (WebSocket + WebRTC)
- CRDT-based state management with Yjs
2026-03-25 11:51:33 +01:00

134 lines
3.2 KiB
TypeScript

import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';
import { WebrtcProvider } from 'y-webrtc';
import { Poll, PollOption } from '../types/poll.types';
const WS_URL = import.meta.env.VITE_WS_URL || 'ws://localhost:3000';
const ROOM_NAME = 'default-poll';
export const ydoc = new Y.Doc();
export const yPolls = ydoc.getArray<Poll>('polls');
export let wsProvider: WebsocketProvider | null = null;
export let webrtcProvider: WebrtcProvider | null = null;
export function initializeProviders() {
wsProvider = new WebsocketProvider(
WS_URL + '/yjs',
ROOM_NAME,
ydoc,
{ connect: true }
);
webrtcProvider = new WebrtcProvider(
ROOM_NAME,
ydoc,
{
signaling: [WS_URL.replace('ws://', 'wss://').replace('http://', 'https://') + '/signal'],
password: null,
awareness: wsProvider.awareness,
maxConns: 20,
filterBcConns: true,
peerOpts: {}
}
);
wsProvider.on('status', (event: { status: string }) => {
console.log('WebSocket status:', event.status);
});
webrtcProvider.on('synced', (synced: boolean) => {
console.log('WebRTC synced:', synced);
});
return { wsProvider, webrtcProvider };
}
export function destroyProviders() {
wsProvider?.destroy();
webrtcProvider?.destroy();
}
export function createPoll(question: string, createdBy: string): string {
const pollId = Math.random().toString(36).substr(2, 9);
const poll: Poll = {
id: pollId,
question,
createdBy,
timestamp: Date.now(),
options: []
};
yPolls.push([poll]);
return pollId;
}
export function addOption(pollId: string, text: string, createdBy: string): void {
const polls = yPolls.toArray();
const pollIndex = polls.findIndex(p => p.id === pollId);
if (pollIndex !== -1) {
const poll = polls[pollIndex];
const option: PollOption = {
id: Math.random().toString(36).substr(2, 9),
text,
votes: 0,
votedBy: [],
createdBy,
timestamp: Date.now()
};
const updatedPoll = {
...poll,
options: [...poll.options, option]
};
yPolls.delete(pollIndex, 1);
yPolls.insert(pollIndex, [updatedPoll]);
}
}
export function voteForOption(pollId: string, optionId: string, userId: string): void {
const polls = yPolls.toArray();
const pollIndex = polls.findIndex(p => p.id === pollId);
if (pollIndex !== -1) {
const poll = polls[pollIndex];
const optionIndex = poll.options.findIndex(opt => opt.id === optionId);
if (optionIndex !== -1) {
const option = poll.options[optionIndex];
if (option.votedBy.includes(userId)) {
return;
}
const updatedOption = {
...option,
votes: option.votes + 1,
votedBy: [...option.votedBy, userId]
};
const updatedOptions = [...poll.options];
updatedOptions[optionIndex] = updatedOption;
const updatedPoll = {
...poll,
options: updatedOptions
};
yPolls.delete(pollIndex, 1);
yPolls.insert(pollIndex, [updatedPoll]);
}
}
}
export function getPolls(): Poll[] {
return yPolls.toArray();
}
export function getPoll(pollId: string): Poll | undefined {
return yPolls.toArray().find(p => p.id === pollId);
}