Make messaging work

This commit is contained in:
Alex Erdei 2025-05-07 17:22:06 +01:00
parent 5937e75de6
commit 4ef4db8781
3 changed files with 181 additions and 49 deletions

View File

@ -315,8 +315,6 @@ function openSocket() {
/* ---------------------------------------------------- */
const parseMsg = (raw) => (typeof raw === "string" ? JSON.parse(raw) : raw);
/* helper does the row belong to me? */
const isMe = (row) => row && row.user_id === localStorage.getItem(LS_USER_ID);
@ -334,11 +332,11 @@ function openSocket() {
/* inside openSocket() unchanged except the helper */
hub.on("fakebook.users.post", (raw) => {
store.dispatch(usersUpdated([parseMsg(raw)]));
store.dispatch(usersUpdated([JSON.parse(raw)]));
});
hub.on("fakebook.users.put", (raw) => {
const row = parseMsg(raw);
const row = JSON.parse(raw);
store.dispatch(usersUpdated([row]));
@ -352,6 +350,22 @@ function openSocket() {
hub.on("fakebook.posts.put", (raw) => {
store.dispatch(postsUpdated([JSON.parse(raw)]));
});
/* helper my uid so we filter only my messages */
const myUID = () => localStorage.getItem(LS_USER_ID);
/* ---------- new message created -------------------------------- */
hub.on("fakebook.message.post", (raw) => {
const msg = mapRestMessage(JSON.parse(raw)); // ← reuse the mapper
if (msg.recipient === myUID())
store.dispatch(incomingMessagesUpdated([msg]));
if (msg.sender === myUID())
store.dispatch(outgoingMessagesUpdated([msg]));
});
})
.catch((err) => {
@ -839,45 +853,86 @@ export function subscribeMessages(kind = "incoming") {
cancelled = true;
};
}
export function uploadMessage(msg) {
DB.messages.push({ ...msg, id: genId(), timestamp: nowISO(), isRead: false });
persist();
return Promise.resolve();
}
export function updateToBeRead(id) {
const m = DB.messages.find((m) => m.id === id);
if (m) {
m.isRead = true;
persist();
}
return Promise.resolve();
}
/* --------------------------------------------------------------
/* Ensure demo DB has at least one user */
uploadMessage(msg)
if (!DB.users.length) {
DB.users.push({
userID: genId(),
firstname: "Demo",
msg = { recipient, text, photoURL? }
lastname: "User",
RETURNS { id } // new message_id
profilePictureURL: "fakebook-avatar.jpeg",
-------------------------------------------------------------- */
backgroundPictureURL: "background-server.jpg",
export async function uploadMessage(msg) {
const sender = localStorage.getItem(LS_USER_ID);
photos: [],
if (!sender) throw new Error("Not authenticated");
posts: [],
const message_id = genId(); // Magic table PK
isOnline: 0,
const body = {
message_id,
isEmailVerified: true,
sender, // who sends
index: 0,
recipient: msg.recipient, // who receives
text: msg.text ?? "",
photoURL: msg.photoURL ?? "",
isPhoto: msg.photoURL ? 1 : 0,
isRead: 0, // unread on insert
/* timestamp is generated by the server */
};
await $fetch(`${API_BASE}/message`, {
method: "POST",
body: JSON.stringify(body),
});
persist();
/* No local dispatch necessary the hubs `message.post` event
will arrive in a few ms and update Redux for both parties. */
return { id: message_id };
}
/* ------------------------------------------------------------------
updateToBeRead(message_id)
flips the flag in Redux immediately (optimistic)
then notifies the server; no need to wait for a hub echo
------------------------------------------------------------------ */
export async function updateToBeRead(message_id) {
/* 1⃣ optimistic local patch (affects whichever slice holds the row) */
const patch = [{ message_id, isRead: 1 }];
store.dispatch(incomingMessagesUpdated(patch));
store.dispatch(outgoingMessagesUpdated(patch));
/* 2⃣ tell the server ignore the hub echo, we no longer listen */
try {
await $fetch(`${API_BASE}/message`, {
method: "PUT",
body: JSON.stringify({ message_id, isRead: 1 }),
});
} catch (err) {
console.warn("[updateToBeRead] PUT failed:", err.message);
// Optional: revert optimistic change on error
}
}

View File

@ -1,13 +1,55 @@
import { createSlice } from "@reduxjs/toolkit";
/* Helper to keep the array ordered (oldest newest).
If you dont care about ordering you can remove the sort. */
const byTimestampAsc = (a, b) =>
new Date(a.timestamp).valueOf() - new Date(b.timestamp).valueOf();
export const incomingMessagesSlice = createSlice({
name: "incomingMessages",
/* simple array of message objects */
initialState: [],
reducers: {
/* ------------------------------------------------------------------
incomingMessagesUpdated
payload is ALWAYS an array (can be 1 or many rows)
for every row: if new append
if exist merge/patch
------------------------------------------------------------------ */
incomingMessagesUpdated: (state, action) => {
const updatedState = [];
action.payload.forEach((message) => updatedState.push(message));
return updatedState;
action.payload.forEach((msg) => {
/* support both id and message_id just in case */
const msgId = msg.id ?? msg.message_id;
const idx = state.findIndex((m) => (m.id ?? m.message_id) === msgId);
if (idx === -1) {
/* ① brand-new message → push */
state.push(msg);
} else {
/* ② already stored → shallow merge keeps other fields */
state[idx] = { ...state[idx], ...msg };
}
});
/* keep messages sorted for deterministic rendering */
state.sort(byTimestampAsc);
/* Immer lets us “mutate” state directly; no return needed */
},
},
});

View File

@ -1,13 +1,48 @@
import { createSlice } from "@reduxjs/toolkit";
/* Optional: keep the list ordered (oldest → newest). */
const byTimestampAsc = (a, b) =>
new Date(a.timestamp).valueOf() - new Date(b.timestamp).valueOf();
export const outgoingMessagesSlice = createSlice({
name: "outgoingMessages",
initialState: [],
initialState: [], // simple array of message objects
reducers: {
/* ------------------------------------------------------------------
outgoingMessagesUpdated
payload is ALWAYS an array (1 or many rows)
merge-in logic keeps existing rows and patches them if needed
------------------------------------------------------------------ */
outgoingMessagesUpdated: (state, action) => {
const updatedState = [];
action.payload.forEach((message) => updatedState.push(message));
return updatedState;
action.payload.forEach((msg) => {
const msgId = msg.id ?? msg.message_id; // tolerate either key
const idx = state.findIndex((m) => (m.id ?? m.message_id) === msgId);
if (idx === -1) {
/* ① brand-new row → append */
state.push(msg);
} else {
/* ② existing row → shallow merge (e.g. read flag flips) */
state[idx] = { ...state[idx], ...msg };
}
});
/* Keep deterministic order for rendering (remove if unnecessary). */
state.sort(byTimestampAsc);
/* Immer lets us mutate in place; no return statement required */
},
},
});