|
|
|
@ -20,31 +20,32 @@ export interface RoomMessage { |
|
|
|
// The message contents.
|
|
|
|
message: string; |
|
|
|
|
|
|
|
// Whether the server has acknowledged the message in a response.
|
|
|
|
// Only ever false for messages we sent.
|
|
|
|
acked: boolean; |
|
|
|
|
|
|
|
// TODO: timestamp.
|
|
|
|
// The time at which this message was received. `null` if unacked.
|
|
|
|
receivedAt: Date | null; |
|
|
|
} |
|
|
|
|
|
|
|
// Attempts to ack `message` upon receival of `messageContents` from `userName`.
|
|
|
|
// Attempts to ack `existing` upon receival of `received`.
|
|
|
|
//
|
|
|
|
// Returns true if `message` was unacked, was sent by `userName` and contains
|
|
|
|
// `messageContents`, in which case `message.acked` is set to `true`.
|
|
|
|
// Returns true if `existing` was unacked and matched `received`, in which case
|
|
|
|
// `existing.receivedAt` is set to `received.receivedAt`.
|
|
|
|
// Returns false otherwise.
|
|
|
|
function maybeAckMessage( |
|
|
|
message: RoomMessage, |
|
|
|
userName: string, |
|
|
|
messageContents: string |
|
|
|
existing: RoomMessage, |
|
|
|
received: RoomMessage |
|
|
|
): boolean { |
|
|
|
if (message.acked) { |
|
|
|
if (existing.receivedAt !== null) { |
|
|
|
return false; // Already acked.
|
|
|
|
} |
|
|
|
if (message.userName !== userName || message.message !== messageContents) { |
|
|
|
|
|
|
|
if (existing.userName !== received.userName) { |
|
|
|
return false; // Wrong user.
|
|
|
|
} |
|
|
|
|
|
|
|
if (existing.message !== received.message) { |
|
|
|
return false; // Wrong message.
|
|
|
|
} |
|
|
|
|
|
|
|
message.acked = true; |
|
|
|
existing.receivedAt = received.receivedAt; |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
@ -95,8 +96,12 @@ const initialState: RoomSliceState = { |
|
|
|
rooms: {}, |
|
|
|
}; |
|
|
|
|
|
|
|
// The payload of an action to send a message to a chat room.
|
|
|
|
export interface RoomMessagePayload { |
|
|
|
export interface RoomReceiveMessagePayload { |
|
|
|
roomName: string; |
|
|
|
message: RoomMessage; |
|
|
|
} |
|
|
|
|
|
|
|
export interface RoomSendMessagePayload { |
|
|
|
roomName: string; |
|
|
|
userName: string; |
|
|
|
message: string; |
|
|
|
@ -125,16 +130,12 @@ export const roomSlice = createSlice({ |
|
|
|
}, |
|
|
|
roomReceiveMessage: ( |
|
|
|
state: RoomSliceState, |
|
|
|
action: PayloadAction<RoomMessagePayload> |
|
|
|
action: PayloadAction<RoomReceiveMessagePayload> |
|
|
|
) => { |
|
|
|
const { roomName, userName } = action.payload; |
|
|
|
const messageContents = action.payload.message; |
|
|
|
const { roomName, message } = action.payload; |
|
|
|
const room = state.rooms[roomName]; |
|
|
|
if (room === undefined) { |
|
|
|
console.log( |
|
|
|
`Unknown room ${roomName} received message from ` + |
|
|
|
`${userName}: ${messageContents}` |
|
|
|
); |
|
|
|
console.log(`Unknown room ${roomName} received message:`, message); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
@ -145,17 +146,17 @@ export const roomSlice = createSlice({ |
|
|
|
// - using a map of unacked contents to messages
|
|
|
|
//
|
|
|
|
for (let i = room.messages.length - 1; i >= 0; i--) { |
|
|
|
const message = room.messages[i]; |
|
|
|
if (maybeAckMessage(message, userName, messageContents)) { |
|
|
|
const existing = room.messages[i]; |
|
|
|
if (maybeAckMessage(existing, message)) { |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
room.messages.push({ userName, message: messageContents, acked: true }); |
|
|
|
room.messages.push(message); |
|
|
|
}, |
|
|
|
roomSendMessage: ( |
|
|
|
state: RoomSliceState, |
|
|
|
action: PayloadAction<RoomMessagePayload> |
|
|
|
action: PayloadAction<RoomSendMessagePayload> |
|
|
|
) => { |
|
|
|
const { roomName, userName, message } = action.payload; |
|
|
|
const room = state.rooms[roomName]; |
|
|
|
@ -166,7 +167,7 @@ export const roomSlice = createSlice({ |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
room.messages.push({ userName, message, acked: false }); |
|
|
|
room.messages.push({ userName, message, receivedAt: null }); |
|
|
|
}, |
|
|
|
roomSetAll: (state: RoomSliceState, action: PayloadAction<RoomState[]>) => { |
|
|
|
state.rooms = {}; |
|
|
|
|