// composables/usePoll.ts import { ref, onMounted, onUnmounted } from 'vue'; import * as Y from 'yjs'; export const usePoll = () => { // Reactive Vue state to drive the UI const pollData = ref>({}); const isConnected = ref(false); const connectedPeers = ref(1); // 1 = just you initially let ydoc: Y.Doc; let provider: any; let yMap: Y.Map; onMounted(async () => { // 1. Initialize the Yjs document ydoc = new Y.Doc(); // 2. Connect via WebRTC. // 'nuxt-p2p-poll-demo-room' is the shared room name. // Anyone using this exact string will sync together. const { WebrtcProvider } = await import('y-webrtc'); provider = new WebrtcProvider('nuxt-p2p-poll-demo-room', ydoc); // Track connection status provider.on('synced', (arg: {synced: boolean}) => { isConnected.value = arg.synced; }); provider.on('peers', (data: any) => { // data.webrtcPeers contains the connected peer IDs connectedPeers.value = data.webrtcPeers.length + 1; // +1 for self }); // 3. Define our shared state structure. We'll use a Map // where the Key is the Poll Option (string) and Value is the Vote Count (number) yMap = ydoc.getMap('shared-poll'); // 4. Listen for changes from other peers and update our Vue reactive state yMap.observe(() => { pollData.value = yMap.toJSON(); }); // Initial load in case data already exists in the room pollData.value = yMap.toJSON(); }); onUnmounted(() => { // Clean up when the component is destroyed if (provider) { provider.disconnect(); } if (ydoc) { ydoc.destroy(); } }); // Action: Add a new option to the poll const addOption = (optionName: string) => { const trimmedName = optionName.trim(); if (trimmedName && !yMap.has(trimmedName)) { yMap.set(trimmedName, 0); // Initialize with 0 votes } }; // Action: Vote for an existing option const vote = (optionName: string) => { if (yMap.has(optionName)) { const currentVotes = yMap.get(optionName) || 0; yMap.set(optionName, currentVotes + 1); } }; return { pollData, isConnected, connectedPeers, addOption, vote }; };