99 lines
3.2 KiB
TypeScript
99 lines
3.2 KiB
TypeScript
// composables/usePoll.ts
|
|
import { ref, watch, onUnmounted } from 'vue';
|
|
import * as Y from 'yjs';
|
|
|
|
export const usePoll = (pollId: Ref<string | null>) => {
|
|
const pollData = ref<PollData>({});
|
|
const isConnected = ref(false);
|
|
const connectionAttempFailed = ref(false);
|
|
const connectedPeers = ref(1);
|
|
|
|
let ydoc: Y.Doc | null = null;
|
|
let provider: any = null;
|
|
let yMap: Y.Map<SignedData<VoteData>[]> | null = null;
|
|
|
|
const cleanup = () => {
|
|
if (provider) provider.disconnect();
|
|
if (ydoc) ydoc.destroy();
|
|
isConnected.value = false;
|
|
pollData.value = {};
|
|
};
|
|
|
|
const initPoll = async (id: string) => {
|
|
cleanup(); // Clear previous session
|
|
|
|
ydoc = new Y.Doc();
|
|
|
|
// 1. Fetch Snapshot from Nuxt API
|
|
try {
|
|
const response = await $fetch<{ update: number[] | null }>(`/api/polls/${id}`);
|
|
if (response?.update) {
|
|
Y.applyUpdate(ydoc, new Uint8Array(response.update));
|
|
}
|
|
} catch (err) {
|
|
console.error('Persistence fetch failed', err);
|
|
}
|
|
|
|
yMap = ydoc.getMap<SignedData<VoteData>[]>('shared-poll');
|
|
|
|
// 2. Local State Sync
|
|
yMap.observe(() => {
|
|
pollData.value = yMap!.toJSON();
|
|
saveStateToServer(id);
|
|
});
|
|
pollData.value = yMap.toJSON();
|
|
|
|
// 3. P2P Connection
|
|
const { WebrtcProvider } = await import('y-webrtc');
|
|
provider = new WebrtcProvider(`nuxt-p2p-${id}`, ydoc, {
|
|
signaling: ["ws:localhost:4444", "ws:lynxpi.ddns.net:4444"]
|
|
});
|
|
|
|
provider.on('synced', (arg: {synced: boolean}) => isConnected.value = arg.synced);
|
|
provider.on('peers', (data: any) => connectedPeers.value = data.webrtcPeers.length + 1);
|
|
};
|
|
|
|
const saveStateToServer = async (id: string) => {
|
|
if (!ydoc) return;
|
|
const stateUpdate = Y.encodeStateAsUpdate(ydoc);
|
|
await $fetch(`/api/polls/${id}`, {
|
|
method: 'POST',
|
|
body: { update: Array.from(stateUpdate) }
|
|
}).catch(() => {});
|
|
};
|
|
|
|
// Watch for ID changes (e.g., user clicks a link or goes back)
|
|
watch(pollId, (newId) => {
|
|
if (newId && import.meta.client) {
|
|
initPoll(newId);
|
|
} else {
|
|
cleanup();
|
|
}
|
|
}, { immediate: true });
|
|
|
|
onUnmounted(cleanup);
|
|
|
|
const addOption = (optionName: string) => {
|
|
if (yMap && !yMap.has(optionName)) yMap.set(optionName, []);
|
|
};
|
|
|
|
const vote = (optionName: string, uuid: string) => {
|
|
if (yMap?.has(optionName)) {
|
|
var voteData : SignedData<VoteData>[] | undefined = yMap.get(optionName)
|
|
if(voteData != undefined){
|
|
var unsignedVoteData : VoteData = {
|
|
userid: uuid,
|
|
timestamp: new Date().toISOString()
|
|
}
|
|
var newVote : SignedData<VoteData> = {
|
|
data: unsignedVoteData,
|
|
signature: "",
|
|
}
|
|
voteData?.push(newVote)
|
|
yMap.set(optionName, voteData);
|
|
}
|
|
}
|
|
};
|
|
|
|
return { pollData, isConnected, connectionAttempFailed, connectedPeers, addOption, vote };
|
|
}; |