最近在做安全方面的项目,有个需求是在用户访问页面和关闭页面的时候,发送对应的数据。
刚拿到需求的时候,觉得没啥东西,
init
的时候发送一次,页面
unload
的时候发送一次就行了,很简单,后面开发了一下,又根据当前项目,发现没这么简单
用户在页面访问时发送数据到后台,页面关闭时也发送数据到后台。
很简单的一句话
但是我们前面说了,没有这么简单,那是因为我们的项目比较复杂
项目是一个庞大的项目,内部有好多子系统,子系统是通过 iframe 内嵌的
点击
nav
模块进入到子系统,当前页面的
hash
不会改变,只会改变
location.pathname
和
document.title
,但是这两个改变有没有事件监听到
点击进入子系统时,也需要对之前的模块进行关闭上报和当前模块的访问上报
iframe
内嵌的项目不需要单独上报,在
top
层进行上报即可
iframe
内嵌的项目单独通过
URL
访问,则和当前项目一样,需要访问上报和关闭上报
经过分析,整体需求分为如下几个点:
第一次进入页面时触发页面访问
刷新当前页面时触发页面访问
新 tab 进入页面时触发页面访问
当前页面点击 nav 进入其他模块时,触发页面关闭&页面访问
关闭页面时触发页面关闭
主要包含以下几点:
cookie
存储
sessionStorage
存储
addEventListener
事件监听
navigator.sendBeacon
数据发送
使用
cookie
主要是因为项目的
domain
都一样,存储不同页面的
title
、
href
、
referrer
等数据
【Cookie】
这个主要是对在当前
tab
页下的跳转进行判断,用来区分是否首次进入当前
tab
【SessionStorage】
事件监听,注意是用来监听
unload
事件。
【addEventListener MDN】
这个我们后面在水一篇文章,单独讲讲,本期只讲用法
navigator.sendBeacon()
方法可用于通过
HTTP
POST
将少量数据异步传输到
Web
服务器。
navigator.sendBeacon(url);
navigator.sendBeacon(url, data);
url
参数表明
data
将要被发送到的网络地址。
data
参数是将要发送的
ArrayBuffer
、
ArrayBufferView
、
Blob
、
DOMString
、
FormData
或
URLSearchParams
类型的数据。
当用户代理成功把数据加入传输队列时,
sendBeacon()
方法将会返回
true
,否则返回
false
。
【navigator.sendBeacon】
无
session
无
cookie
调用
urlDetectChange
函数
触发
openPage()
进行页面访问上报
设置
session
字段
setTimeout
轮询,设置
cookie
字段,监听
URL
变化
有
session
有
cookie
触发页面访问上报
setTimeout
轮询,监听
URL
变化
有
session
有
cookie
setTimeout
轮询,当前
document
中的
title
和
href
与
cookie
中的不一致时,进行之前页面关闭上报和当前页面访问上报
设置新的
cookie
值
继续
setTimeout
轮询,监听
URL
变化
有
cookie
无
session
触发页面访问上报
设置新的
cookie
继续
setTimeout
轮询,监听
URL
变化
unload
/**
* 上报接口
* @param {object} data 上报接口参数
* @returns {boolean} sendBeacon 接口返回信息
*/
export const sendBeaconMessage = (data: object): boolean =>
window.navigator.sendBeacon(
'xxx',
JSON.stringify(data)
)
// Cookies 使用 js-cookie
/**
* 设置 cookie 值
* 设置 href、pageTitle、referrer 字段
* key 为 ACCESS_CLOSE_COOKIE_NAME
* domain 为 '.xxx.com'
*/
export const setAccessPageCookie = () =>
Cookies.set(
'ACCESS_CLOSE_COOKIE_NAME',
JSON.stringify({
href: location.href,
pageTitle: document.title,
referrer: document.referrer
}),
{ domain: '.xxx.com', expires: 30 }
)
/**
* 页面访问发送消息
* @param {object} data 上报接口参数
* 设置 sessionStorage 的值,
*/
export const openPageSendBeacon = async (data: object) => {
sessionStorage.setItem('ACCESS_CLOSE_SESSION_NAME', 'ISTRUE')
const sendBeaconSusscess = sendBeaconMessage(data)
// 打印 sendBeaconSusscess 的值
console.log(
'%c client:sendDataToRemote use sendBeacon access page: %o',
'color: green;',
sendBeaconSusscess
)
}
/**
* 页面关闭发送消息
* @param {object} data 上报接口参数
*/
export const closePageSendBeacon = async (data: object) => {
const sendBeaconSusscess = sendBeaconMessage(data)
// 打印 sendBeaconSusscess 的值
console.log(
'%c client:sendDataToRemote use sendBeacon close page: %o, ',
'color: red;',
sendBeaconSusscess
)
}
/**
* URL 改变事件
*/
export const urlDetectChange = () => {
const accessPageData = Cookies.get('ACCESS_CLOSE_COOKIE_NAME')
? JSON.parse(Cookies.get('ACCESS_CLOSE_COOKIE_NAME'))
: null
const sessionAccessData = sessionStorage.getItem('ACCESS_CLOSE_SESSION_NAME')
// 第一次进入页面 和 新 tab 进入页面
if (!accessPageData || !sessionAccessData) {
openPageSendBeacon({})
}
setTimeout(() => {
if (
accessPageData &&
location.href !== accessPageData.href &&
document.title !== accessPageData.pageTitle &&
sessionAccessData
) {
// 点击 nav 进入其他模块
closePageSendBeacon({})
openPageSendBeacon({})
}
setAccessPageCookie()
urlDetectChange()
}, 1000)
}
// 加这个是针对 iframe 内部的项目不进行监听,只在 top 层进行数据上报
if (window.top === window.self) {
// 页面访问上报 刷新页面
sessionStorage.getItem('ACCESS_CLOSE_SESSION_NAME') && openPageSendBeacon({})
urlDetectChange()
window.addEventListener('unload', () => closePageSendBeacon({}))
}
hash
hash
navigator.sendBeacon