chrome浏览器多页签唯一关闭时自动注销

整体思路

技术背景:vue单页面应用
整体思路按如下问题展开:

  1. 多页签如何唯一?
  2. 页签唯一了,页签间如何共享信息?
  3. 页签间共享信息解决了,能否监听浏览器关闭动作?
  4. 浏览器关闭动作监听到了,如何自动注销?
  5. 自动注销实现了,是否还存在其他问题?

由上述问题可知,其中自动注销的最简单的实现,基本上就是清除浏览器记录的用户登录信息,一般都是清除相关的cookie。

多页签唯一思考

我们知道自动注销无非就是设置cookie的信息过期,实现自动清除用户的登录信息。用户在浏览器上对于同一系统只打开一个浏览器页签时,这样的实现是可以的。打开了多个页签的话,用此方式只能实现单点登出,即随意关闭一个页签导致其他页签全部注销了,这样的不符合需求和用户习惯的。我们现在的难题在于怎样识别是否只剩唯一一个页签。因此我们需要探索window对象的属性,找出可以标识页签的唯一的属性,这样就可以知道用户打开了哪些页签和关闭了哪些页签,以及最后只剩哪个页签。

探索window对象name的属性

我们通过网上查看window对象的相关属性,发现name属性可以标识页签的唯一

window.name的特征

  1. 每个页签都可以设置window.name,设置后的页签是独立的,无法打开相同window.name的页签;
  2. 可以直接赋值name,直接获取则返回当前页签名称,window.name默认是空字符串
  3. window.name的数据类型是string,传递其他类型的数据都会转成string;

页签唯一实现

在页面加载时,如果window.name为空,则设置为UUID ,非空则跳过,说明已设置。

//main.js里插入以下代码
const uuid = require('uuid')
if(window.name===''){
   window.name=uuid.v4(); 
}

页签间共享信息思考

浏览器的存储对象有cookies、sessionStorage、localStorage,这里我们根据作者”pengc“的博客:cookies、sessionStorage和localStorage解释及区别,得知chrome使用localStorage最优,使用cookies会浪费带宽。
使用 localStorage 可以创建永不过期的本地存储的 name/value 对,而且value只能为string类型。我们需要记录多个打开的页签window.name,因此需要构建一个数组,而且其中window.name不能重复,然后转成json字符串存储。在页签关闭时去删除对应数组的元素就实现了页签的数量管理。

页签数量的管理实现

使用 localStorage创建key为Page-uuid,value为数组转成的json字符串的键值对,围绕这个键值对实现三个核心方法:获取页签的数量方法getPageNum,记录页签的window.name方法setPageUuid,清除数组中的页签记录removePageUuid。

//可以写到utils.js
const PageUuidKey = 'Page-uuid';
//获取页签的数量方法
export function getPageNum() {
    //获取原有的数组的json字符串
    let pageStr=localStorage.getItem(PageUuidKey);
    if(pageStr){
        //转成json数组
        let pageList=JSON.parse(pageStr);
        if(pageList){
            //返回数组长度
            return pageList.length;
        }
    }
    //其他情况直接返回0
    return 0;
}
//记录页签的window.name
export function setPageUuid(_uuid) {
    //有效毫秒数,根据需要设置
    let validPeriod=4000
    //获取原有的数组的json字符串
    let pageStr=localStorage.getItem(PageUuidKey);
    if(pageStr){
        //转成json数组
        let pageList=JSON.parse(pageStr);
        if(pageList){
            //判断是否已设置过该window.name
            if(isExistsKV(pageList,'name',_uuid)){
                //已设置过该window.name,则重新设置失效时间
                let index=getItemIndexByKeyValue(pageList,'name',_uuid);
                pageList[index].expireTime=new Date().getTime()+validPeriod
            }else{
                 //未设置过该window.name,则插入记录
                pageList.push({name:_uuid,expireTime:new Date().getTime()+validPeriod});
            }
            //更新localStorage的多页签记录
            localStorage.setItem(PageUuidKey,JSON.stringify(pageList));
        }
    }else{
       //没有设置过,则新增localStorage的多页签记录
       localStorage.setItem(PageUuidKey,JSON.stringify([{name:_uuid,expireTime:new Date().getTime()+validPeriod}]));
    }
}
//清除数组中的页签记录
export function removePageUuid(_uuid) {
    //获取原有的数组的json字符串
    let pageStr=localStorage.getItem(PageUuidKey);
    if(pageStr){
         //转成json数组
        let pageList=JSON.parse(pageStr);
        if(pageList){
            //获取需要清除的记录的数组下标
            let index=getItemIndexByKeyValue(pageList,'name',_uuid);
            if(index!==-1){
                //清除页签记录
                pageList.splice(index,1);
                //更新localStorage多页签记录
                localStorage.setItem(PageUuidKey,JSON.stringify(pageList));
            }
        }
    }
}
// 判断数组是否存在该对象
export function isExistsKV(items, key,value) {
    for(var i=0,len=items.length;i<len;i++){
        if(items[i][key]===value){
            return true
        }
    }
    return false
}
// 根据键值对获取对应的元素下标
export function getItemIndexByKeyValue(items,  key,value) {
    for(var i=0,len=items.length;i<len;i++){
        if(items[i][key]===value){
            return i
        }
    }
    return -1
}

监听chrome浏览器页签关闭动作思考

这里不讨论其他浏览器的关闭监听,有需要可以看看作者”云_飞扬“的博客:js监听浏览器关闭事件(区分刷新和关闭,兼容IE9,10,11,Edge,Chrome和Firefox),必须要感谢网上众多大神给予的帮助。
通过网上查找资料得知,chrome浏览器刷新和关闭都会按顺序触发window.onbeforeunload事件和window.onunload事件,两个事件间的时差长短可以作为判断页签的动作属于关闭还是刷新,不超过5毫秒的(<=5)的是关闭,否则是刷新。本人的测试了很多次,大部分时间是这样的,小部分出现问题。一直不知道这个原理是什么,望有大神能慷慨解答下。
关闭动作我们可以监听到了,页签唯一也设置了,那如何得知打开的页签只剩一个了,那我们需要找出可以应用共享的存储对象,这样就可以把打开的页签记录到共享的存储对象,关闭的页签则进行清除。

关闭动作监听实现

//单页应用的home主页上插入以下代码
mounted() {
	 //这里省略其他无关的代码。。。
	 
	 //监听刷新和关闭的相关事件
	 window.addEventListener('beforeunload', e => this.beforeunloadHandler(e))
	 window.addEventListener('unload', e => this.unloadHandler(e))
	 //监听页签切换,清除监测页签定时器,下文再述	
	 document.addEventListener('visibilitychange',e => this.clearCheckPageUuid(e))
},
methods: {
     //这里省略其他无关的代码。。。
     
     //window.onbeforeunload事件处理
     beforeunloadHandler(e) {
		 this.beforeUnloadTime = new Date().getTime();
     },
     //window.onunload事件处理
	 unloadHandler(e) {
		 let _gap_time = new Date().getTime() - this.beforeUnloadTime;	
		//判断是窗口关闭还是刷新
		if (_gap_time <= 5) {
			//关闭处理
			this.closeHandler();
		}
	 },
	 //关闭处理
    closeHandler() {
		if (getPageNum() <= 1) {
			//注销,下文再述	
			this.logout()
		} else {
		    //关闭当前页面
            this.closeCurrentPage()
		}
	},
	//关闭当前页面
	closeCurrentPage(){
	    //注册页签定时器,下文再述	
	    window.clearInterval(registerPageUuidInterval)
	    //监测页签定时器,下文再述	
        window.clearInterval(checkPageUuidInterval)	
        //清除数组中的页签记录
		removePageUuid(window.name);
	}
},
destroyed() {
	//这里省略其他无关的代码。。。
	//注销,下文再述	
	this.logout()	
	//清除监听刷新和关闭的相关事件
	window.removeEventListener('beforeunload', e => this.beforeunloadHandler(e))
	window.removeEventListener('unload', e => this.unloadHandler(e))
	//清除页签切换的监听,下文再述	
	document.removeEventListener('visibilitychange',e => this.clearCheckPageUuid(e))
	//清除注册页签定时器,下文再述	
	window.clearInterval(registerPageUuidInterval)
	//清除监测页签定时器,下文再述	
    window.clearInterval(checkPageUuidInterval)		
}

自动注销思考

由于项目的登录信息是存储在cookie上,因此自动注销的实现,就是在页签唯一且关闭时设置cookie的登录信息过期即可。

注销代码实现

import Cookies from 'js-cookie'
methods: {
     //这里省略其他无关的代码。。。
     
    //注销,TokenKey为登录信息的key,自行设置
    logout() {
        //清除所有的多页签记录
        localStorage.removeItem(PageUuidKey);
        //设置过期,清除登录的cookie信息
		Cookies.set(TokenKey, '', {
           path: '/',
           expires: 0
        });
	},
}

测试出来的问题思考

其实在项目测试的过程中发现一些意外的情况我们没法避免,就是可能出现该清除的多页签记录没有清除,导致已经关闭的页面localStorage里还有记录,然后就会导致cookie无法清除,因为页签不可能只剩一个了。哪如何得知localStorage的多页签记录是有效的(即页面是打开的)?本人暂时没找到javascript可以直接根据window.name判断页面是否在打开的状态。如有大神知晓望能指教。
于是本人根据做大数据集群的思维,想到可以利用分布式的心跳机制来监测页面是否在打开的状态。
其实核心的实现就在未写出来的注册页签定时器方法registerPageUuidInterval、监测页签定时器方法checkPageUuidInterval和清除监测页签定时器方法clearCheckPageUuid

页签在打开状态的心跳实现

相信大家注意到在记录页签的window.name的方法中多页签记录的数组元素不是string类型,而是对象类型。关键是这个对象有失效时间expireTime这个属性,利用这个属性我们定时T监听是否失效,失效则清除记录即可;还在打开的页面为了不失效,则定时t去注册延长失效时间ts;为了保证监测页签定时器只有一个,因此需要监听页签的切换。打开多个页签会有多个监测页签定时器同时运行,除了会导致性能问题,还有可能和注册页签定时器产生冲突,因此只有切换到的页签去启动监测页签定时器即可。

这里的时间关系为:2*t<=ts<=T,本文设置为 t =2000毫秒,ts=4000毫秒,T=4000毫秒,具体频率按项目实际设置即可,不宜过短过长,过短则影响浏览器性能,过长则会延时出更多问题。

//可以写到utils.js
export function removeTimeoutPageUuid() {
    let pageStr=localStorage.getItem(PageUuidKey);
    if(pageStr){
        let pageList=JSON.parse(pageStr);
        if(pageList){
            let newList = [];
            let now = new Date().getTime()
            pageList.forEach(item => {
                if(now - item.expireTime<0){
                   newList.push(item)
                }
            });
            pageList = newList
            localStorage.setItem(PageUuidKey,JSON.stringify(pageList));
        }
    }
}
//单页应用的home主页上插入以下代码
//监测页签定时器方法
var checkPageUuidInterval= self.setInterval(()=>{
        //每4秒清空过时的
        removeTimeoutPageUuid()
    },4000);
//注册页签定时器方法
var registerPageUuidInterval= self.setInterval(()=>{
        //每2秒注册pageuuid
        setPageUuid(window.name)
    },2000);
export default {
    //这里省略其他无关的代码。。。
	methods: {
		//这里省略其他无关的代码。。。
		
        //切换页签,清除监测页签定时器方法
		clearCheckPageUuid(e){
			  if(document.visibilityState==='hidden'){
				   window.clearInterval(checkPageUuidInterval)
			  }else{
				   checkPageUuidInterval=self.setInterval(()=>{
                       //每4秒清空过时的
                       removeTimeoutPageUuid()
                   },4000);
                   this.setUserInfo()
			   }
		},
	},
	//这里省略其他无关的代码。。。
}

总结

本文的核心技术实现总结为window.nameuuid实现页签的唯一,localStorage实现页签间的信息共享,监听beforeunloadunload事件实现关闭动作触发,window.setInterval定时器、监听visibilitychange事件和心跳机制实现页签打开状态的监控。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DataVault善战

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值