Make SignalR connection work on users' online status
This commit is contained in:
parent
1ae6adb349
commit
2bf2c4d9dd
|
@ -8,6 +8,7 @@
|
|||
"name": "fakebook-ainiro",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@microsoft/signalr": "^8.0.7",
|
||||
"@reduxjs/toolkit": "^1.8.3",
|
||||
"bootstrap": "^4.6.0",
|
||||
"preact": "^10.25.3",
|
||||
|
@ -16,7 +17,8 @@
|
|||
"react-icons": "^4.2.0",
|
||||
"react-player": "^2.12.0",
|
||||
"react-redux": "^8.0.2",
|
||||
"react-router-dom": "^5.2.0"
|
||||
"react-router-dom": "^5.2.0",
|
||||
"signalr": "^2.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@preact/preset-vite": "^2.9.3",
|
||||
|
@ -832,6 +834,19 @@
|
|||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@microsoft/signalr": {
|
||||
"version": "8.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-8.0.7.tgz",
|
||||
"integrity": "sha512-PHcdMv8v5hJlBkRHAuKG5trGViQEkPYee36LnJQx4xHOQ5LL4X0nEWIxOp5cCtZ7tu+30quz5V3k0b1YNuc6lw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"abort-controller": "^3.0.0",
|
||||
"eventsource": "^2.0.2",
|
||||
"fetch-cookie": "^2.0.3",
|
||||
"node-fetch": "^2.6.7",
|
||||
"ws": "^7.4.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
|
@ -1306,6 +1321,18 @@
|
|||
"integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"event-target-shim": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.5"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-plugin-transform-hook-names": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-transform-hook-names/-/babel-plugin-transform-hook-names-1.0.2.tgz",
|
||||
|
@ -1629,6 +1656,34 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/event-target-shim": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/eventsource": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz",
|
||||
"integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fetch-cookie": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-2.2.0.tgz",
|
||||
"integrity": "sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==",
|
||||
"license": "Unlicense",
|
||||
"dependencies": {
|
||||
"set-cookie-parser": "^2.4.8",
|
||||
"tough-cookie": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
|
@ -1726,8 +1781,7 @@
|
|||
"version": "3.7.1",
|
||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
|
||||
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
|
@ -1841,6 +1895,26 @@
|
|||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/node-html-parser": {
|
||||
"version": "6.1.13",
|
||||
"resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz",
|
||||
|
@ -1985,6 +2059,33 @@
|
|||
"react": ">=0.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/psl": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
|
||||
"integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"punycode": "^2.3.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/lupomontero"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/querystringify": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
||||
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
|
||||
|
@ -2228,6 +2329,12 @@
|
|||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/reselect": {
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
|
||||
|
@ -2299,6 +2406,21 @@
|
|||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/signalr": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/signalr/-/signalr-2.4.3.tgz",
|
||||
"integrity": "sha512-RbBKFVCZvDgyyxZDeu6Yck9T+diZO07GB0bDiKondUhBY1H8JRQSOq8R0pLkf47ddllQAssYlp7ckQAeom24mw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"jquery": ">=1.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
|
||||
|
@ -2341,6 +2463,27 @@
|
|||
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tough-cookie": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
|
||||
"integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"psl": "^1.1.33",
|
||||
"punycode": "^2.1.1",
|
||||
"universalify": "^0.2.0",
|
||||
"url-parse": "^1.5.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/uncontrollable": {
|
||||
"version": "7.2.1",
|
||||
"resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz",
|
||||
|
@ -2365,6 +2508,15 @@
|
|||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
||||
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz",
|
||||
|
@ -2396,6 +2548,16 @@
|
|||
"browserslist": ">= 4.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-parse": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"querystringify": "^2.1.1",
|
||||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
|
||||
|
@ -2492,6 +2654,43 @@
|
|||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "7.5.10",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
|
||||
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
|
|
|
@ -9,15 +9,17 @@
|
|||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"preact": "^10.25.3",
|
||||
"@microsoft/signalr": "^8.0.7",
|
||||
"@reduxjs/toolkit": "^1.8.3",
|
||||
"bootstrap": "^4.6.0",
|
||||
"preact": "^10.25.3",
|
||||
"react-bootstrap": "^1.5.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-icons": "^4.2.0",
|
||||
"react-player": "^2.12.0",
|
||||
"react-redux": "^8.0.2",
|
||||
"react-router-dom": "^5.2.0"
|
||||
"react-router-dom": "^5.2.0",
|
||||
"signalr": "^2.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@preact/preset-vite": "^2.9.3",
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import userReducer from "../features/user/userSlice";
|
||||
import currentUserReducer from "../features/currentUser/currentUserSlice";
|
||||
import usersReducer from "../features/users/usersSlice";
|
||||
import postsReducer from "../features/posts/postsSlice";
|
||||
import incomingMessagesReducer from "../features/incomingMessages/incomingMessagesSlice";
|
||||
import outgoingMessagesReducer from "../features/outgoingMessages/outgoingMessagesSlice";
|
||||
import imagesReducer from "../features/images/imagesSlice";
|
||||
import linkReducer from "../features/link/linkSlice";
|
||||
import accountPageReducer from "../features/accountPage/accountPageSlice";
|
||||
|
||||
export default configureStore({
|
||||
reducer: {
|
||||
user: userReducer,
|
||||
currentUser: currentUserReducer,
|
||||
users: usersReducer,
|
||||
posts: postsReducer,
|
||||
incomingMessages: incomingMessagesReducer,
|
||||
outgoingMessages: outgoingMessagesReducer,
|
||||
images: imagesReducer,
|
||||
link: linkReducer,
|
||||
accountPage: accountPageReducer,
|
||||
},
|
||||
});
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
===================================================================== */
|
||||
|
||||
import * as signalR from "@microsoft/signalr";
|
||||
|
||||
import store from "../app/store";
|
||||
|
||||
import {
|
||||
|
@ -30,6 +32,8 @@ import { outgoingMessagesUpdated } from "../features/outgoingMessages/outgoingMe
|
|||
|
||||
const API_BASE = "https://alexerdei-team.us.ainiro.io/magic/modules/fakebook";
|
||||
|
||||
const SOCKETS_URL = "wss://alexerdei-team.us.ainiro.io/sockets";
|
||||
|
||||
const REGISTER_URL = `${API_BASE}/register`;
|
||||
|
||||
const LOGIN_URL = `${API_BASE}/login`;
|
||||
|
@ -63,14 +67,8 @@ function setAuth(token, user_id) {
|
|||
localStorage.setItem(LS_TOKEN, token);
|
||||
|
||||
localStorage.setItem(LS_USER_ID, user_id);
|
||||
}
|
||||
|
||||
function clearAuth() {
|
||||
authToken = authUser = null;
|
||||
|
||||
localStorage.removeItem(LS_TOKEN);
|
||||
|
||||
localStorage.removeItem(LS_USER_ID);
|
||||
openSocket();
|
||||
}
|
||||
|
||||
/* --------------------------- Utilities -------------------------------- */
|
||||
|
@ -101,58 +99,6 @@ async function $fetch(url, opts = {}) {
|
|||
return data;
|
||||
}
|
||||
|
||||
/* ---------------------- REST → UI mapper ------------------------------- */
|
||||
|
||||
function addPath(u, fileName) {
|
||||
if (typeof fileName !== "string" || !fileName.length) return fileName;
|
||||
|
||||
if (fileName.includes("/")) return fileName; /* already has folder */
|
||||
|
||||
return `${u.user_id}/${fileName}`; /* prepend owner id */
|
||||
}
|
||||
|
||||
function mapRestUser(u) {
|
||||
const photosRaw = JSON.parse(u.photos || "[]");
|
||||
|
||||
const photos = photosRaw.map((item) => {
|
||||
if (typeof item === "string") {
|
||||
/* legacy: array of strings */
|
||||
|
||||
return { filename: addPath(u, item) };
|
||||
}
|
||||
|
||||
if (item && typeof item.filename === "string") {
|
||||
return { ...item, filename: addPath(u, item.filename) };
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
return {
|
||||
userID: u.user_id,
|
||||
|
||||
firstname: u.firstname,
|
||||
|
||||
lastname: u.lastname,
|
||||
|
||||
/* already stored as folder/filename → leave untouched */
|
||||
|
||||
profilePictureURL: u.profilePictureURL,
|
||||
|
||||
backgroundPictureURL: u.backgroundPictureURL,
|
||||
|
||||
photos,
|
||||
|
||||
posts: JSON.parse(u.posts || "[]"),
|
||||
|
||||
isOnline: !!u.isOnline,
|
||||
|
||||
isEmailVerified: !!u.isEmailVerified,
|
||||
|
||||
index: u.index ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
/* ===================================================================== *
|
||||
|
||||
|
||||
|
@ -310,6 +256,109 @@ export function sendPasswordReminder(email) {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* SignalR hub – one connection shared across the app */
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
let hub = null;
|
||||
|
||||
function openSocket() {
|
||||
if (hub) return; // already connected / connecting
|
||||
|
||||
const token = localStorage.getItem(LS_TOKEN);
|
||||
|
||||
if (!token) return; // not logged-in → no live updates
|
||||
|
||||
hub = new signalR.HubConnectionBuilder()
|
||||
|
||||
.withUrl(SOCKETS_URL, {
|
||||
accessTokenFactory: () => token,
|
||||
|
||||
skipNegotiation: true, // no CORS pre-flight
|
||||
|
||||
transport: signalR.HttpTransportType.WebSockets, // WebSocket only
|
||||
})
|
||||
|
||||
.withAutomaticReconnect()
|
||||
|
||||
.configureLogging(signalR.LogLevel.Warning)
|
||||
|
||||
.build();
|
||||
|
||||
/* ---------------------------------------------------- */
|
||||
|
||||
/* helper – SignalR sends a JSON-string → return POJO */
|
||||
|
||||
/* ---------------------------------------------------- */
|
||||
|
||||
const parseMsg = (raw) => (typeof raw === "string" ? JSON.parse(raw) : raw);
|
||||
|
||||
/* 1️⃣ Start first … */
|
||||
|
||||
hub
|
||||
.start()
|
||||
|
||||
.then(() => {
|
||||
console.info("[SignalR] connected, id:", hub.connectionId);
|
||||
|
||||
/* 2️⃣ … then register listeners ----------------------------- */
|
||||
|
||||
/* inside openSocket() – unchanged except the helper */
|
||||
|
||||
hub.on("fakebook.users.post", (raw) => {
|
||||
store.dispatch(usersUpdated([parseMsg(raw)]));
|
||||
});
|
||||
|
||||
hub.on("fakebook.users.put", (raw) => {
|
||||
store.dispatch(usersUpdated([parseMsg(raw)]));
|
||||
});
|
||||
})
|
||||
|
||||
.catch((err) => {
|
||||
console.warn("[SignalR] start failed:", err);
|
||||
|
||||
hub = null; // let auto-reconnect retry
|
||||
});
|
||||
|
||||
/* extra diagnostics ---------------------------------------------- */
|
||||
|
||||
hub.onreconnecting((err) => console.warn("[SignalR] reconnecting:", err));
|
||||
|
||||
hub.onreconnected((id) => console.info("[SignalR] reconnected, id:", id));
|
||||
|
||||
hub.onclose((err) => console.warn("[SignalR] closed:", err));
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/* Close SignalR + forget credentials */
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
function clearAuth() {
|
||||
/* forget in-memory copies */
|
||||
|
||||
authToken = null;
|
||||
|
||||
authUser = null;
|
||||
|
||||
/* forget persisted copies */
|
||||
|
||||
localStorage.removeItem(LS_TOKEN); // "fakebook.jwt"
|
||||
|
||||
localStorage.removeItem(LS_USER_ID); // "fakebook.user_id"
|
||||
|
||||
/* close the hub if it exists */
|
||||
|
||||
if (hub) {
|
||||
hub.stop(); // graceful shutdown → returns a promise we don’t await
|
||||
|
||||
hub = null;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===================================================================== *
|
||||
|
||||
SECTION B — IN-MEMORY MOCK (UNCHANGED) *
|
||||
|
@ -372,7 +421,7 @@ export function subscribeCurrentUser() {
|
|||
const meRow = users.find((u) => u.user_id === user_id);
|
||||
|
||||
if (meRow && !cancelled) {
|
||||
store.dispatch(currentUserUpdated(mapRestUser(meRow)));
|
||||
store.dispatch(currentUserUpdated(meRow));
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("[subscribeCurrentUser] failed:", err.message);
|
||||
|
@ -447,7 +496,7 @@ export function subscribeUsers() {
|
|||
});
|
||||
|
||||
if (!cancelled) {
|
||||
store.dispatch(usersUpdated(users.map(mapRestUser)));
|
||||
store.dispatch(usersUpdated(users));
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("[subscribeUsers] failed:", err.message);
|
||||
|
|
|
@ -1,38 +1,54 @@
|
|||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
import mapRestUser from "../../utils/mapRestUser"; // same helper the users slice uses
|
||||
|
||||
export const currentUserSlice = createSlice({
|
||||
name: "currentUser",
|
||||
initialState: {
|
||||
firstname: "",
|
||||
lastname: "",
|
||||
profilePictureURL: "fakebook-avatar.jpeg",
|
||||
backgroundPictureURL: "background-server.jpg",
|
||||
photos: [],
|
||||
posts: [],
|
||||
isOnline: false,
|
||||
},
|
||||
|
||||
initialState: null, // stays a single object, not an array
|
||||
|
||||
reducers: {
|
||||
/* ------------------------------------------------------------------
|
||||
|
||||
currentUserUpdated
|
||||
|
||||
– can receive either a *full* user object (first load / login)
|
||||
|
||||
– or a *partial* patch coming from SignalR (online flag etc.)
|
||||
|
||||
------------------------------------------------------------------ */
|
||||
|
||||
currentUserUpdated: (state, action) => {
|
||||
const {
|
||||
firstname,
|
||||
lastname,
|
||||
profilePictureURL,
|
||||
backgroundPictureURL,
|
||||
photos,
|
||||
posts,
|
||||
isOnline,
|
||||
index,
|
||||
} = action.payload;
|
||||
state.firstname = firstname;
|
||||
state.lastname = lastname;
|
||||
state.profilePictureURL = profilePictureURL;
|
||||
state.backgroundPictureURL = backgroundPictureURL;
|
||||
state.isOnline = isOnline;
|
||||
if (index) state.index = index;
|
||||
state.photos = [];
|
||||
state.posts = [];
|
||||
photos.forEach((photo) => state.photos.push(photo));
|
||||
posts.forEach((post) => state.posts.push(post));
|
||||
const raw = action.payload;
|
||||
|
||||
/* first call or explicit full replacement ---------------------- */
|
||||
|
||||
if (
|
||||
state === null ||
|
||||
(raw.firstname !== undefined && raw.lastname !== undefined)
|
||||
) {
|
||||
return mapRestUser(raw); // normalise every field once
|
||||
}
|
||||
|
||||
/* otherwise treat it as a PATCH -------------------------------- */
|
||||
|
||||
const next = { ...state }; // Immer lets us mutate but explicit copy is clear
|
||||
|
||||
if (raw.isOnline !== undefined) next.isOnline = !!raw.isOnline;
|
||||
|
||||
if (raw.firstname !== undefined) next.firstname = raw.firstname;
|
||||
|
||||
if (raw.lastname !== undefined) next.lastname = raw.lastname;
|
||||
|
||||
if (raw.profilePictureURL !== undefined)
|
||||
next.profilePictureURL = raw.profilePictureURL;
|
||||
|
||||
if (raw.backgroundPictureURL !== undefined)
|
||||
next.backgroundPictureURL = raw.backgroundPictureURL;
|
||||
|
||||
/* add similar guards if more fields can arrive partially … */
|
||||
|
||||
return next; // Immer will take care of immutability
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,13 +1,52 @@
|
|||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import mapRestUser from "../../utils/mapRestUser";
|
||||
|
||||
export const usersSlice = createSlice({
|
||||
name: "users",
|
||||
initialState: [],
|
||||
reducers: {
|
||||
usersUpdated: (state, action) => {
|
||||
const updatedState = [];
|
||||
action.payload.forEach((user) => updatedState.push(user));
|
||||
return updatedState;
|
||||
/* action.payload will always be an *array* */
|
||||
|
||||
const incoming = action.payload;
|
||||
|
||||
incoming.forEach((raw) => {
|
||||
const userID = raw.user_id ?? raw.userID;
|
||||
|
||||
const idx = state.findIndex((u) => u.userID === userID);
|
||||
|
||||
if (idx === -1) {
|
||||
/* -------- NEW USER ----------------------------------------- */
|
||||
|
||||
state.push(mapRestUser(raw)); // map every field
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* -------- EXISTING USER -------------------------------------- */
|
||||
|
||||
const cur = state[idx];
|
||||
|
||||
const patch = {};
|
||||
|
||||
/* Online flag comes as 0/1; convert to boolean if present */
|
||||
|
||||
if (raw.isOnline !== undefined) patch.isOnline = !!raw.isOnline;
|
||||
|
||||
if (raw.firstname !== undefined) patch.firstname = raw.firstname;
|
||||
|
||||
if (raw.lastname !== undefined) patch.lastname = raw.lastname;
|
||||
|
||||
if (raw.profilePictureURL !== undefined)
|
||||
patch.profilePictureURL = raw.profilePictureURL;
|
||||
|
||||
if (raw.backgroundPictureURL !== undefined)
|
||||
patch.backgroundPictureURL = raw.backgroundPictureURL;
|
||||
|
||||
/* add other fields you expect to arrive partially … */
|
||||
|
||||
state[idx] = { ...cur, ...patch }; // keep old fields
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/* utils/mapRestUser.js
|
||||
|
||||
Converts a “magic” REST / SignalR user row into the shape Fakebook
|
||||
|
||||
components already consume. */
|
||||
|
||||
function addPath(row, fileName) {
|
||||
if (typeof fileName !== "string" || !fileName.length) return fileName;
|
||||
|
||||
if (fileName.includes("/")) return fileName; // already has folder
|
||||
|
||||
return `${row.user_id}/${fileName}`; // prepend owner id
|
||||
}
|
||||
|
||||
export default function mapRestUser(row) {
|
||||
const photosRaw = JSON.parse(row.photos || "[]");
|
||||
|
||||
const photos = photosRaw.map((item) => {
|
||||
if (typeof item === "string") {
|
||||
return { filename: addPath(row, item) }; // legacy array
|
||||
}
|
||||
|
||||
if (item && typeof item.filename === "string") {
|
||||
return { ...item, filename: addPath(row, item.filename) };
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
return {
|
||||
userID: row.user_id,
|
||||
|
||||
firstname: row.firstname,
|
||||
|
||||
lastname: row.lastname,
|
||||
|
||||
profilePictureURL: row.profilePictureURL,
|
||||
|
||||
backgroundPictureURL: row.backgroundPictureURL,
|
||||
|
||||
photos,
|
||||
|
||||
posts: JSON.parse(row.posts || "[]"),
|
||||
|
||||
isOnline: !!row.isOnline,
|
||||
|
||||
isEmailVerified: !!row.isEmailVerified,
|
||||
|
||||
index: row.index ?? 0,
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue