Debug when user stays online at closing browser
This commit is contained in:
parent
b2affa91c7
commit
545413d2ce
|
@ -153,34 +153,22 @@ const openSocket = () => {
|
|||
});
|
||||
};
|
||||
|
||||
// auth helpers -----------------------------------------------------------
|
||||
|
||||
const setAuth = (t, u) => {
|
||||
authToken = t;
|
||||
authUser = u;
|
||||
saveAuthToStorage(t, u);
|
||||
openSocket();
|
||||
};
|
||||
|
||||
const clearAuth = () => {
|
||||
authToken = null;
|
||||
authUser = null;
|
||||
clearAuthStorage();
|
||||
if (hub) {
|
||||
hub.stop();
|
||||
hub = null;
|
||||
}
|
||||
};
|
||||
|
||||
// presence ---------------------------------------------------------------
|
||||
|
||||
const patchOnline = async (on) => {
|
||||
// patchOnline(on, keep = false) ← keep=true will add keepalive: true
|
||||
|
||||
const patchOnline = async (on, keep = false) => {
|
||||
if (!authUser) return;
|
||||
|
||||
try {
|
||||
await $fetch(USERS_URL, {
|
||||
await fetch(USERS_URL, {
|
||||
method: "PUT",
|
||||
|
||||
headers: { "Content-Type": "application/json", ...authHeader() },
|
||||
|
||||
body: JSON.stringify({ user_id: authUser, isOnline: on ? 1 : 0 }),
|
||||
|
||||
...(keep ? { keepalive: true } : {}),
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn("[online] PUT failed:", e.message);
|
||||
|
@ -191,6 +179,77 @@ export const currentUserOnline = () => patchOnline(true);
|
|||
|
||||
export const currentUserOffline = () => patchOnline(false);
|
||||
|
||||
// leave / background handlers -------------------------------------------
|
||||
|
||||
let leaveHandlerInstalled = false;
|
||||
|
||||
const sendOfflineKeepalive = () => patchOnline(false, true);
|
||||
|
||||
const handleVisibility = () => {
|
||||
if (document.visibilityState === "hidden") sendOfflineKeepalive();
|
||||
|
||||
if (document.visibilityState === "visible") currentUserOnline();
|
||||
};
|
||||
|
||||
const installLeaveHandlers = () => {
|
||||
if (leaveHandlerInstalled) return;
|
||||
|
||||
window.addEventListener("pagehide", sendOfflineKeepalive); // tab / window close
|
||||
|
||||
window.addEventListener("freeze", sendOfflineKeepalive); // page-lifecycle freeze
|
||||
|
||||
window.addEventListener("resume", currentUserOnline); // page-lifecycle resume
|
||||
|
||||
document.addEventListener("visibilitychange", handleVisibility);
|
||||
|
||||
leaveHandlerInstalled = true;
|
||||
};
|
||||
|
||||
const removeLeaveHandlers = () => {
|
||||
if (!leaveHandlerInstalled) return;
|
||||
|
||||
window.removeEventListener("pagehide", sendOfflineKeepalive);
|
||||
|
||||
window.removeEventListener("freeze", sendOfflineKeepalive);
|
||||
|
||||
window.removeEventListener("resume", currentUserOnline);
|
||||
|
||||
document.removeEventListener("visibilitychange", handleVisibility);
|
||||
|
||||
leaveHandlerInstalled = false;
|
||||
};
|
||||
|
||||
// setAuth … add
|
||||
|
||||
const setAuth = (t, u) => {
|
||||
authToken = t;
|
||||
|
||||
authUser = u;
|
||||
|
||||
saveAuthToStorage(t, u);
|
||||
|
||||
openSocket();
|
||||
|
||||
installLeaveHandlers(); // ← here
|
||||
};
|
||||
|
||||
// clearAuth … add
|
||||
|
||||
const clearAuth = () => {
|
||||
authToken = null;
|
||||
|
||||
authUser = null;
|
||||
|
||||
clearAuthStorage();
|
||||
|
||||
if (hub) {
|
||||
hub.stop();
|
||||
hub = null;
|
||||
}
|
||||
|
||||
removeLeaveHandlers(); // ← here
|
||||
};
|
||||
|
||||
// bootstrap after login/restore -----------------------------------------
|
||||
|
||||
const bootstrapSession = async (uid) => {
|
||||
|
|
|
@ -15,10 +15,10 @@ import FriendsListPage from "./FriendsListPage";
|
|||
/* Router */
|
||||
|
||||
import {
|
||||
BrowserRouter as Router,
|
||||
Switch,
|
||||
Route,
|
||||
useLocation,
|
||||
BrowserRouter as Router,
|
||||
Switch,
|
||||
Route,
|
||||
useLocation,
|
||||
} from "react-router-dom";
|
||||
|
||||
/* Layout */
|
||||
|
@ -30,19 +30,19 @@ import Container from "react-bootstrap/Container";
|
|||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import {
|
||||
friendsListPageSet,
|
||||
profileLinkSet,
|
||||
watchSet,
|
||||
friendsListPageSet,
|
||||
profileLinkSet,
|
||||
watchSet,
|
||||
} from "../features/accountPage/accountPageSlice";
|
||||
|
||||
/* Mock-backend helpers */
|
||||
|
||||
import {
|
||||
currentUserOffline,
|
||||
currentUserOnline,
|
||||
subscribeCurrentUser,
|
||||
subscribeUsers,
|
||||
subscribePosts,
|
||||
currentUserOffline,
|
||||
currentUserOnline,
|
||||
subscribeCurrentUser,
|
||||
subscribeUsers,
|
||||
subscribePosts,
|
||||
} from "../backend/backend";
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
@ -52,158 +52,139 @@ import {
|
|||
/* ------------------------------------------------------------------ */
|
||||
|
||||
const RouteStateSync = () => {
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const location = useLocation();
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
const { pathname } = location;
|
||||
useEffect(() => {
|
||||
const { pathname } = location;
|
||||
|
||||
/* friends list page */
|
||||
/* friends list page */
|
||||
|
||||
dispatch(friendsListPageSet(pathname.startsWith("/fakebook/friends/list")));
|
||||
dispatch(friendsListPageSet(pathname.startsWith("/fakebook/friends/list")));
|
||||
|
||||
/* watch page (videos feed) */
|
||||
/* watch page (videos feed) */
|
||||
|
||||
dispatch(watchSet(pathname.startsWith("/fakebook/watch")));
|
||||
}, [location, dispatch]);
|
||||
dispatch(watchSet(pathname.startsWith("/fakebook/watch")));
|
||||
}, [location, dispatch]);
|
||||
|
||||
return null; // renders nothing
|
||||
return null; // renders nothing
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
const UserAccount = () => {
|
||||
const dispatch = useDispatch();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
/* Redux selectors */
|
||||
/* Redux selectors */
|
||||
|
||||
const profileLink = useSelector((state) => state.accountPage.profileLink);
|
||||
const profileLink = useSelector((state) => state.accountPage.profileLink);
|
||||
|
||||
const currentUser = useSelector((state) => state.currentUser);
|
||||
const currentUser = useSelector((state) => state.currentUser);
|
||||
|
||||
const users = useSelector((state) => state.users);
|
||||
const users = useSelector((state) => state.users);
|
||||
|
||||
/* -------------------------------------------------- */
|
||||
/* -------------------------------------------------- */
|
||||
|
||||
/* Firestore-like subscriptions & online/offline flag */
|
||||
/* Firestore-like subscriptions */
|
||||
|
||||
/* -------------------------------------------------- */
|
||||
/* -------------------------------------------------- */
|
||||
|
||||
useEffect(() => {
|
||||
const unsubCurrentUser = subscribeCurrentUser();
|
||||
useEffect(() => {
|
||||
const unsubCurrentUser = subscribeCurrentUser();
|
||||
|
||||
const unsubUsers = subscribeUsers();
|
||||
const unsubUsers = subscribeUsers();
|
||||
|
||||
const unsubPosts = subscribePosts();
|
||||
const unsubPosts = subscribePosts();
|
||||
|
||||
/* mark user online */
|
||||
/* mark user online */
|
||||
|
||||
currentUserOnline();
|
||||
currentUserOnline();
|
||||
|
||||
/* window closed or refreshed */
|
||||
/* cleanup */
|
||||
|
||||
const beforeUnload = () => currentUserOffline();
|
||||
return () => {
|
||||
unsubCurrentUser();
|
||||
|
||||
window.addEventListener("beforeunload", beforeUnload);
|
||||
unsubUsers();
|
||||
|
||||
/* tab visibility switch */
|
||||
unsubPosts();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const visChange = () =>
|
||||
document.visibilityState === "visible"
|
||||
? currentUserOnline()
|
||||
: currentUserOffline();
|
||||
/* -------------------------------------------------- */
|
||||
|
||||
document.addEventListener("visibilitychange", visChange);
|
||||
/* Build unique profile link (.index appended once) */
|
||||
|
||||
/* cleanup */
|
||||
/* -------------------------------------------------- */
|
||||
|
||||
return () => {
|
||||
unsubCurrentUser();
|
||||
useEffect(() => {
|
||||
if (!currentUser) return;
|
||||
|
||||
unsubUsers();
|
||||
/* remove any existing trailing ".number" */
|
||||
|
||||
unsubPosts();
|
||||
const base = profileLink.replace(/\.\d+$/, "");
|
||||
|
||||
window.removeEventListener("beforeunload", beforeUnload);
|
||||
const newLink =
|
||||
currentUser.index && currentUser.index > 0
|
||||
? `${base}.${currentUser.index}`
|
||||
: base;
|
||||
|
||||
document.removeEventListener("visibilitychange", visChange);
|
||||
};
|
||||
}, []);
|
||||
dispatch(profileLinkSet(newLink));
|
||||
}, [currentUser, profileLink, dispatch]);
|
||||
|
||||
/* -------------------------------------------------- */
|
||||
/* Loading guard */
|
||||
|
||||
/* Build unique profile link (.index appended once) */
|
||||
if (!currentUser || users.length === 0) {
|
||||
return <div>…Loading</div>;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------- */
|
||||
/* -------------------------------------------------- */
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentUser) return;
|
||||
/* Render */
|
||||
|
||||
/* remove any existing trailing ".number" */
|
||||
/* -------------------------------------------------- */
|
||||
|
||||
const base = profileLink.replace(/\.\d+$/, "");
|
||||
return (
|
||||
<div className="bg-200 vw-100 main-container overflow-hidden">
|
||||
<Container className="w-100 p-0" fluid>
|
||||
<Router>
|
||||
<RouteStateSync />
|
||||
|
||||
const newLink =
|
||||
currentUser.index && currentUser.index > 0
|
||||
? `${base}.${currentUser.index}`
|
||||
: base;
|
||||
<TitleBar />
|
||||
|
||||
dispatch(profileLinkSet(newLink));
|
||||
}, [currentUser, profileLink, dispatch]);
|
||||
<Switch>
|
||||
{/* Friends list ------------------------------------------------ */}
|
||||
|
||||
/* Loading guard */
|
||||
<Route path="/fakebook/friends/list" component={FriendsListPage} />
|
||||
|
||||
if (!currentUser || users.length === 0) {
|
||||
return <div>…Loading</div>;
|
||||
}
|
||||
{/* Single photo ----------------------------------------------- */}
|
||||
|
||||
/* -------------------------------------------------- */
|
||||
<Route path="/fakebook/photo/:userID/:n" component={PhotoViewer} />
|
||||
|
||||
/* Render */
|
||||
{/* Watch (video feed) ----------------------------------------- */}
|
||||
|
||||
/* -------------------------------------------------- */
|
||||
<Route
|
||||
path="/fakebook/watch"
|
||||
render={(props) => <HomePage {...props} className="pt-5" />}
|
||||
/>
|
||||
|
||||
return (
|
||||
<div className='bg-200 vw-100 main-container overflow-hidden'>
|
||||
<Container className='w-100 p-0' fluid>
|
||||
<Router>
|
||||
<RouteStateSync />
|
||||
{/* User profile ----------------------------------------------- */}
|
||||
|
||||
<TitleBar />
|
||||
<Route path="/fakebook/:userName" component={Profile} />
|
||||
|
||||
<Switch>
|
||||
{/* Friends list ------------------------------------------------ */}
|
||||
{/* News-feed root --------------------------------------------- */}
|
||||
|
||||
<Route path='/fakebook/friends/list' component={FriendsListPage} />
|
||||
|
||||
{/* Single photo ----------------------------------------------- */}
|
||||
|
||||
<Route path='/fakebook/photo/:userID/:n' component={PhotoViewer} />
|
||||
|
||||
{/* Watch (video feed) ----------------------------------------- */}
|
||||
|
||||
<Route
|
||||
path='/fakebook/watch'
|
||||
render={(props) => <HomePage {...props} className='pt-5' />}
|
||||
/>
|
||||
|
||||
{/* User profile ----------------------------------------------- */}
|
||||
|
||||
<Route path='/fakebook/:userName' component={Profile} />
|
||||
|
||||
{/* News-feed root --------------------------------------------- */}
|
||||
|
||||
<Route
|
||||
path='/fakebook'
|
||||
exact
|
||||
render={(props) => <HomePage {...props} className='pt-5' />}
|
||||
/>
|
||||
</Switch>
|
||||
</Router>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
<Route
|
||||
path="/fakebook"
|
||||
exact
|
||||
render={(props) => <HomePage {...props} className="pt-5" />}
|
||||
/>
|
||||
</Switch>
|
||||
</Router>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserAccount;
|
||||
|
|
Loading…
Reference in New Issue