158 lines
4.7 KiB
TypeScript
158 lines
4.7 KiB
TypeScript
import { onMessage, sendToPeer, broadcast } from './peer.js';
|
|
import { getUserId } from './crypto.js';
|
|
import { getPollById, updatePollInStore, setRole, removeRole, setPollStatus } from './stores/polls.svelte.js';
|
|
import { canVote, canAddOption, canManageUsers, canManagePoll } from './permissions.js';
|
|
import type { Message, Poll, PollOption, Vote } from './types.js';
|
|
|
|
const processedCommands = new Set<string>();
|
|
let revision = 0;
|
|
|
|
export function startHosting(): () => void {
|
|
const unsub = onMessage(async (msg, peerId) => {
|
|
const userId = await getUserId();
|
|
|
|
switch (msg.type) {
|
|
case 'sync:request': {
|
|
const poll = getPollById(msg.payload.pollId);
|
|
if (poll && poll.ownerId === userId) {
|
|
sendToPeer(peerId, { type: 'poll:state', payload: poll });
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'poll:vote': {
|
|
if (processedCommands.has(msg.commandId)) {
|
|
sendToPeer(peerId, { type: 'ack', payload: { commandId: msg.commandId, revision } });
|
|
break;
|
|
}
|
|
|
|
const poll = getPollById(msg.payload.pollId);
|
|
if (!poll || poll.ownerId !== userId) break;
|
|
|
|
if (!canVote(poll, peerId)) {
|
|
sendToPeer(peerId, {
|
|
type: 'error',
|
|
payload: { commandId: msg.commandId, message: 'Not authorized to vote' }
|
|
});
|
|
break;
|
|
}
|
|
|
|
const vote: Vote = {
|
|
optionId: msg.payload.optionId,
|
|
voterId: poll.anonymous ? null : peerId,
|
|
timestamp: msg.payload.timestamp
|
|
};
|
|
|
|
const filteredVotes = poll.anonymous
|
|
? poll.votes
|
|
: poll.votes.filter((v) => v.voterId !== peerId);
|
|
|
|
const updated: Poll = { ...poll, votes: [...filteredVotes, vote] };
|
|
await updatePollInStore(updated);
|
|
revision++;
|
|
processedCommands.add(msg.commandId);
|
|
|
|
sendToPeer(peerId, { type: 'ack', payload: { commandId: msg.commandId, revision } });
|
|
broadcast({ type: 'poll:state', payload: updated }, peerId);
|
|
break;
|
|
}
|
|
|
|
case 'poll:option:add': {
|
|
if (processedCommands.has(msg.commandId)) {
|
|
sendToPeer(peerId, { type: 'ack', payload: { commandId: msg.commandId, revision } });
|
|
break;
|
|
}
|
|
|
|
const poll2 = getPollById(msg.payload.pollId);
|
|
if (!poll2 || poll2.ownerId !== userId) break;
|
|
|
|
if (!canAddOption(poll2, peerId)) {
|
|
sendToPeer(peerId, {
|
|
type: 'error',
|
|
payload: { commandId: msg.commandId, message: 'Not authorized to add options' }
|
|
});
|
|
break;
|
|
}
|
|
|
|
const option: PollOption = {
|
|
id: msg.payload.id,
|
|
text: msg.payload.text,
|
|
addedBy: peerId,
|
|
addedAt: msg.payload.addedAt
|
|
};
|
|
|
|
const updated2: Poll = { ...poll2, options: [...poll2.options, option] };
|
|
await updatePollInStore(updated2);
|
|
revision++;
|
|
processedCommands.add(msg.commandId);
|
|
|
|
sendToPeer(peerId, { type: 'ack', payload: { commandId: msg.commandId, revision } });
|
|
broadcast({ type: 'poll:state', payload: updated2 }, peerId);
|
|
break;
|
|
}
|
|
|
|
case 'poll:role:update': {
|
|
if (processedCommands.has(msg.commandId)) {
|
|
sendToPeer(peerId, { type: 'ack', payload: { commandId: msg.commandId, revision } });
|
|
break;
|
|
}
|
|
|
|
const poll3 = getPollById(msg.payload.pollId);
|
|
if (!poll3 || poll3.ownerId !== userId) break;
|
|
|
|
if (!canManageUsers(poll3, peerId)) {
|
|
sendToPeer(peerId, {
|
|
type: 'error',
|
|
payload: { commandId: msg.commandId, message: 'Not authorized to manage users' }
|
|
});
|
|
break;
|
|
}
|
|
|
|
await setRole(msg.payload.pollId, msg.payload.userId, msg.payload.role);
|
|
revision++;
|
|
processedCommands.add(msg.commandId);
|
|
|
|
const refreshed = getPollById(msg.payload.pollId);
|
|
sendToPeer(peerId, { type: 'ack', payload: { commandId: msg.commandId, revision } });
|
|
if (refreshed) broadcast({ type: 'poll:state', payload: refreshed }, peerId);
|
|
break;
|
|
}
|
|
|
|
case 'poll:status:update': {
|
|
if (processedCommands.has(msg.commandId)) {
|
|
sendToPeer(peerId, { type: 'ack', payload: { commandId: msg.commandId, revision } });
|
|
break;
|
|
}
|
|
|
|
const poll4 = getPollById(msg.payload.pollId);
|
|
if (!poll4 || poll4.ownerId !== userId) break;
|
|
|
|
if (!canManagePoll(poll4, peerId)) {
|
|
sendToPeer(peerId, {
|
|
type: 'error',
|
|
payload: { commandId: msg.commandId, message: 'Not authorized to manage poll' }
|
|
});
|
|
break;
|
|
}
|
|
|
|
await setPollStatus(msg.payload.pollId, msg.payload.status);
|
|
revision++;
|
|
processedCommands.add(msg.commandId);
|
|
|
|
const refreshed2 = getPollById(msg.payload.pollId);
|
|
sendToPeer(peerId, { type: 'ack', payload: { commandId: msg.commandId, revision } });
|
|
if (refreshed2) broadcast({ type: 'poll:state', payload: refreshed2 }, peerId);
|
|
break;
|
|
}
|
|
|
|
case 'user:profile': {
|
|
// Store peer's profile for display purposes
|
|
// Could be extended to a peer profile cache
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
return unsub;
|
|
}
|