最近在做安全方面的项目,有个需求是在用户访问页面和关闭页面的时候,发送对应的数据。
刚拿到需求的时候,觉得没啥东西,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
等数据
这个主要是对在当前 tab
页下的跳转进行判断,用来区分是否首次进入当前 tab
事件监听,注意是用来监听 unload
事件。
这个我们后面在水一篇文章,单独讲讲,本期只讲用法
navigator.sendBeacon()
方法可用于通过 HTTP
POST
将少量数据异步传输到 Web
服务器。
navigator.sendBeacon(url);
navigator.sendBeacon(url, data);
url
参数表明 data
将要被发送到的网络地址。
data
参数是将要发送的 ArrayBuffer
、ArrayBufferView
、Blob
、DOMString
、FormData
或 URLSearchParams
类型的数据。
当用户代理成功把数据加入传输队列时,sendBeacon()
方法将会返回 true
,否则返回 false
。
无 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
进行可靠的数据传输