奇葩需求:所有这个网站的标签页 tab 全部关闭则退出登陆(调用 logout 接口)。
(以下代码是目前的实现,完全关闭浏览器后退出登陆)
// 刷新和关闭网页都会触发 onunload, 因此用 sessionStorage 区分,浏览器完全关闭后会清空 sessionStorage
sessionStorage.setItem('reloaded', 'yes');
window.onunload = function(e) {
if (sessionStorage.getItem('reloaded') == null) {
fetch("/logout", {
method: "POST",
// keepalive 属性用于页面卸载时,告诉浏览器在后台保持连接,继续发送数据。开启了 keepalive 属性后,网页就算被关闭了,请求被会继续执行而不会中断。
keepalive: true
});
}
};
请问,有没有方法实现检测到所有同域标签页关闭的方法?
1
encro 2022-08-18 10:27:48 +08:00 1
session 不设置过期时间,默认行为就是这样。
|
2
encro 2022-08-18 10:29:23 +08:00 3
这是后端的坑需要前端填吗?
打脸: 如果客户端异常了,停电了,服务端就不会自动退出吗? |
3
maggch97 2022-08-18 10:39:18 +08:00
localStorage 周期性写时间。下次打开的时候判断时间是不是过期了,过期就 logout
|
4
zjsxwc 2022-08-18 10:47:20 +08:00
“完全关闭浏览器后退出”
不就是没设置到期时间的 session 默认行为吗 |
5
runze 2022-08-18 11:15:39 +08:00 5
|
6
Belmode 2022-08-18 11:15:48 +08:00 via Android
思路错了。浏览器做不到你这需求中的效果,即使有也必定很绕。让服务端来做。
|
7
vanton 2022-08-18 11:37:30 +08:00
让后端去做,前端没这个义务做。
|
8
wunonglin 2022-08-18 11:41:04 +08:00
起 ws 啊,ws 断了服务器就把 session 删了不就好了
|
9
lisongeee 2022-08-18 11:47:02 +08:00 2
你这个代码根本不管用啊,sessionStorage 各个标签页是完全独立的
测试如下代码 ```js sessionStorage.setItem('reloaded', 'yes'); window.onunload = function(e) { if (sessionStorage.getItem('reloaded') == null) { fetch("http://127.0.0.1:8080/on", { method: "GET", keepalive: true }); } else { fetch("http://127.0.0.1:8080/off", { method: "GET", keepalive: true }); } }; ``` 只打开一个标签页然后关闭,结果如下 ![img]( https://github.com/lisonge/src/raw/main/img/Snipaste_2022-08-18_11-43-07.png) |
10
lisongeee 2022-08-18 11:53:19 +08:00 4
一个可能可行的解法是 Service Worker ,它独立于 标签页 存在,你可以在每个标签页 onunload 的时候给 Service Worker postMessage ,然后 Service Worker 去调用 await clients.matchAll() ,如果得到的列表长度是 0 ,就 doYourWork
有一个问题,文档里没有说明 Service Worker 何时被停止,而且我懒得测试,你可以自己试试 |
11
yolee599 2022-08-18 13:04:19 +08:00 via Android 1
如果用户端直接杀浏览器进程或者浏览器崩了,就永远不退出吗?
|
12
viakiba 2022-08-18 14:21:48 +08:00 via Android
量不大心跳就完事了
|
13
nothingistrue 2022-08-18 15:02:54 +08:00
那些让后端处理的,你们是哪里学的开发,浏览器的事件有没有触发,服务器端要拿头去判断吗。现行 HTTP 规范下,只有标签页关闭和窗口关闭两个事件,没有特定标签页全部关闭事件,这事拿常规手段做不了。
我想到一个思路,需要前后端配合。前端,每个页面定时上报自己还活着,可以用 websocket 加心跳,也可以就是单纯的 JavaScript 定时器(如果标签页是频繁打开关闭的,那就千万别用 websocket ,DDOS 了)。后端需要监控当前 Session (如果能定位到客户端也可以把维度换成客户端)的“活着的页面数”,变成零的时候就触发登出操作。 但是,跟产品沟通一下,把需求变成“5 分钟或者半小时内没操作就自动登出”,回是更好的选择。 |
15
nothingistrue 2022-08-18 15:06:06 +08:00
不嫌 LOW ,不怕性能爆炸,并且还不关心是否长时间没操作的话,把会话超时时间定为 1 分钟,然后每个页面都弄个 30 秒的定时器触发垃圾请求,也能大力出奇迹。
|
16
explore365 2022-08-18 15:08:53 +08:00
cookie 定时更新每个 tab 时间戳,检查 tab 列表,时间戳超时则为退出。
|
17
Puteulanus 2022-08-18 15:10:56 +08:00 2
换一个思路,用跨标签页通信呢?
第一个标签页触发登陆之后,登陆凭据不是保存在 cookie 里,而是只存在内存里,从第二个标签页开始,打开时的登陆凭据都靠和已经打开的标签页沟通来拿到 这样所有标签页关闭之后合法登陆凭据消失,下一个“第一个标签页”拿不到凭据,再次触发用户登陆操作 |
18
nothingistrue 2022-08-18 15:31:28 +08:00
刚去看了下 localStorage 、sessionStorage ,发现 sessionStorage 是基于标签页的,这样是有办法监控当前域名打开的标签页的个数的。
思路就是: 用与标签页无关的 localStorage 存储计数,每打开一个标签页就加 1 ,每关闭一个标签页就减 1 ; 用于标签页有关的 sessionStrrage 结合 load unload 事件来触发标签页打开和标签页关闭事件,主要是把刷新标签页给区分出去,怎么区分还要仔细想一想,不是太好处理。 给 unload 事件加个监听,通过 localStorage 的计数,来判断是否需要触发登出处理,也需要区分刷新跟关闭。 |
19
lonenol 2022-08-18 15:35:13 +08:00
需求不合理,直接砍需求。。
|
20
dudubaba 2022-08-18 15:38:16 +08:00
试试 BroadcastChannel 监听
|
21
nothingistrue 2022-08-18 15:40:08 +08:00
还是遇到难题了,新建标签页跟刷新事件很好区分,但是关闭标签页跟刷新事件,不好区分。
|
22
yunye 2022-08-18 15:50:03 +08:00 1
谁提的需求谁自己来实现
|
23
shyling 2022-08-18 16:00:52 +08:00
一个标签连个 ws ,全断了就全关了
|
24
nothingistrue 2022-08-18 16:05:01 +08:00 3
我搜了一圈,没有找到区分 unload 是刷新还是关闭的方法,也就是说没有标签页关闭事件( windows.onclose 还在试验中)。所以这个每必要再研究下去了,就是不可实现的需求,怼回去。就留一个长时间未操作服务器端自动退出就可以了,浏览器端就别搞骚套路了。
|
25
Imindzzz 2022-08-18 16:49:41 +08:00
window.onload = () => {
const count = parseInt(localStorage.getItem('count') || '0', 10); localStorage.setItem('count', count + 1 + ''); const lastExitTime = parseInt(sessionStorage.getItem('lastExitTime') || '0', 10); // 三秒内回来,表示是刷新了 if (lastExitTime && Date.now() - lastExitTime < 30000) { // TODO 发送接口 还原登录状态 } }; window.onbeforeunload = (event) => { const count = parseInt(localStorage.getItem('count') || '0', 10); localStorage.setItem('count', count - 1 + ''); if (count <= 1) { // TODO 但是其实这里无法区分是刷新还是关闭。 需要后端 sessionStorage.setItem('lastExitTime', Date.now().toString()); // TODO 发送接口退出 // 发送网络请求建议使用 sendBeacon https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator/sendBeacon // 注意:页面必须有用户操作才能正常拦截 event.preventDefault(); event.returnValue = '所有都关闭了'; return '所有都关闭了'; } }; |
26
autoxbc 2022-08-18 16:50:37 +08:00
用 BroadcastChannel 向其他标签广播,没有回应说明是最后一个标签,然后用 sendBeacon 向服务器发送信息
|
27
lin07hui 2022-08-18 17:08:16 +08:00
当前的浏览器都没法区分开“关闭标签页跟刷新事件”
|
28
dtdths1 2022-08-18 19:16:00 +08:00
无法监听,over 。你要是想退出可用心跳,让服务端判断
|
29
v2eb 2022-08-18 19:27:16 +08:00 via Android
每个 tab 页分配个唯一 id , 集合形式存到 local storage .tab 页关闭时判断集合长度
|
30
chnwillliu 2022-08-18 19:47:32 +08:00 via Android
@v2eb 关闭和刷新分不出来的,只有一个 tab 一刷新结果登出了?
|
31
chnwillliu 2022-08-18 19:55:24 +08:00 via Android
不妨想想为什么登出一定要调用 logout 接口,不调用会有什么后果?是后端有资源要释放么?用户浏览器直接崩也是可能没机会调用 js 的 unload 的,所以关键逻辑不能依赖 logout 接口。
|
32
zhuweiyou 2022-08-18 21:29:01 +08:00
session 行为就是这样的. 不需要前端干预. 这帮后端不行,还要前端背锅?
|
33
realpg 2022-08-18 21:52:38 +08:00
这需求有啥奇葩的...
这不是动态网页出来这二三十年的最基本的一个操作^ |
34
chnwillliu 2022-08-19 08:13:36 +08:00 via Android 1
@realpg 这还不奇葩?这跟动态网页有什么关系?你哪怕是 windows 客户端也不能保证用户退出就一定能调用到服务端的 logout 接口。我强杀进程,直接强制关机等等,都可能导致客户端直接就消失不调用你的 logout 接口。
logout 是要干什么?统计在线人数么?还是释放啥资源还是什么?非要强依赖客户端在线与否,那只能建立 socket 或者让客服端发心跳。期望客户端恭恭敬敬给你调用 logout ,那只能说无法保证。 |
35
realpg 2022-08-19 12:01:40 +08:00
@chnwillliu #34
现在的开发者连有个东西叫 session,有个东西叫 cookie 都不知道了 |
36
realpg 2022-08-19 12:11:00 +08:00
@chnwillliu #34
而且, 不依赖 cookie, 也可以用 localstorage 和 sessionstorage 实现他要求的掉登录状态机制 |
37
DingJZ 2022-08-19 13:42:09 +08:00
这玩意还真实现了一个,上面大家说 cookie 之类的,是可以,但是后端不改怎么办,整个用户体系都是基于 token 的,改不现实,需求还是提给前端的。
用 BroadcastChannel ,可以 tab 之前通信,退出的时候发通知,所有页面订阅,我把代码放在 nginx 里注入到项目里,这样业务不需要关注 |
38
dtdths1 2022-08-19 16:39:51 +08:00
凡是让客户端主动去调的,不管用什么,全不靠谱。各种异常情况、兼容问题,不要想当然
|
39
chnwillliu 2022-08-20 14:44:01 +08:00 via Android
@realpg 依赖 session 类型的 cookie 然后捏?怎么在合适的时候调用 logout 接口?所以说要改实现,因为没法保证调用后端的 logout 接口。
还有哦,sessionStorage 和 session 类型的 cookie 行为不一致。sessionStorage 不跨 tab. |
40
chnwillliu 2022-08-20 14:48:59 +08:00 via Android
@realpg 对,其实用 session 类型的 cookie 存东西就行,用户关了所有 tab 自动被清掉。用 sessionStorage 存的话要考虑跨 tab 共享问题,解决完这个问题,行为就和 session cookie 一致了。
利用好了,根本就不要什么 logout 接口。 |
41
Jexxie OP 感谢大家,最后改需求了,沿用目前方案,遵循浏览器默认行为,完全关闭浏览器后退出登录。
|