fix: persistent random Snowflake worker ID + clean reaction API

Two fixes addressing architectural review feedback:

1. Snowflake ID collisions: Replace process.pid-based worker ID with a
   cryptographically random value (0-1023) generated once at first boot
   and persisted to instance_settings.worker_id. Eliminates deterministic
   ID collisions between Docker instances that all run as PID 1.

2. Reaction API leak: Revert addReaction/removeReaction signatures to
   (messageId, emoji) — the store now resolves the channel internally by
   scanning its message cache, keeping routing logic out of the UI layer.
This commit is contained in:
Jannis Braun
2026-03-03 00:03:29 +01:00
parent 5194dbef25
commit 72e07c1cc1
6 changed files with 76 additions and 10 deletions
+19 -6
View File
@@ -43,8 +43,8 @@ interface ChatState {
addRealtimeMessage: (channelId: string, message: MessageWithUser) => void;
updateMessage: (message: MessageWithUser) => void;
removeMessage: (messageId: string, channelId: string) => void;
addReaction: (messageId: string, emoji: string, channelId: string) => void;
removeReaction: (messageId: string, emoji: string, channelId: string) => void;
addReaction: (messageId: string, emoji: string) => void;
removeReaction: (messageId: string, emoji: string) => void;
onReactionAdded: (messageId: string, reaction: any) => void;
onReactionRemoved: (messageId: string, userId: string, emoji: string) => void;
setTyping: (channelId: string, userId: string, username: string) => void;
@@ -57,6 +57,16 @@ interface ChatState {
onChannelAck: (channelId: string, messageId: string) => void;
}
/** Find which channel a message belongs to by scanning the message cache. */
function findChannelForMessage(messages: Map<string, MessageWithUser[]>, messageId: string): string | null {
for (const [channelId, msgs] of messages) {
if (msgs.some(m => m.id === messageId)) {
return channelId;
}
}
return null;
}
export const useChatStore = create<ChatState>((set, get) => ({
messages: new Map(),
currentChannelId: null,
@@ -356,13 +366,16 @@ export const useChatStore = create<ChatState>((set, get) => ({
});
},
addReaction: (messageId: string, emoji: string, channelId: string) => {
const origin = getChannelOrigin(channelId);
addReaction: (messageId: string, emoji: string) => {
// Resolve the channel from our message cache so the UI doesn't need to pass it
const channelId = findChannelForMessage(get().messages, messageId);
const origin = channelId ? getChannelOrigin(channelId) : '';
wsSend({ type: 'reaction_add', messageId, emoji }, origin);
},
removeReaction: (messageId: string, emoji: string, channelId: string) => {
const origin = getChannelOrigin(channelId);
removeReaction: (messageId: string, emoji: string) => {
const channelId = findChannelForMessage(get().messages, messageId);
const origin = channelId ? getChannelOrigin(channelId) : '';
wsSend({ type: 'reaction_remove', messageId, emoji }, origin);
},