如何在用户关闭网站时保存用户会话信息。

huangapple go评论63阅读模式
英文:

How to save user session info when the user closes the website

问题

I'm trying to build a user replay session functionality for a website and I'm using rrweb library to do that.

What this library does is when recording: it captures all the events in the webpage and I'm able to save those events by storing them in an array and when I want to replay the session I'll just pass the array to the replay function and that function handles the session replay.

Currently for testing purposes I'm saving this array in my sessionStorage and every time a new event is emitted I'll just get the array then push that new event into it then save the array again in my sessionStorage like this:

rrweb.record({
	emit(event) {
		const sessions = JSON.parse(sessionStorage.getItem('sessions')) || [];
		sessions.push(event);
		sessionStorage.setItem('sessions', JSON.stringify(sessions));
	},
});

However, for production, instead of saving that array in my sessionStorage and then updating it every time a new event is emitted, I would like to save that array in my database and I want to call the function that would save the array to my database once either when the user logs out or when the user decides to close the website (like by pressing the X button).

The first part -when the user logs out- is pretty straight forward, I'll just add an eventListener on the logout button, it's the second part -when the user decides to close the website- that is giving me some headache.

I know that there is the beforeUnload event however after a quick search it became clear to me that it's unreliable so basically what I'm looking for is a reliable way to determine when the user closed my website so I could fire an async function that would save the array to my database.

英文:

I'm trying to build a user replay session functionality for a website and I'm using rrweb library to do that.

What this library does is when recording: it captures all the events in the webpage and I'm able to save those events by storing them in an array and when I want to replay the session I'll just pass the array to the replay function and that function handles the session replay.

Currently for testing purposes I'm saving this array in my sessionStorage and every time a new event is emitted I'll just get the array then push that new event into it then save the array again in my sessionStorage like this:

rrweb.record({
	emit(event) {
		const sessions = JSON.parse(sessionStorage.getItem('sessions')) || [];
		sessions.push(event);
		sessionStorage.setItem('sessions', JSON.stringify(sessions));
	},
});

However, for production, instead of saving that array in my sessionStorage and then updating it every time a new event is emitted, I would like to save that array in my database and I want to call the function that would save the array to my database once either when the user logs out or when the user decides to close the website (like by pressing the X button).

The first part -when the user logs out- is pretty straight forward, I'll just add an eventListener on the logout button, it's the second part -when the user decides to close the website- that is giving me some headache.

I know that there is the beforeUnload event however after a quick search it became clear to me that it's unreliable so basically what I'm looking for is a reliable way to determine when the user closed my website so I could fire an async function that would save the array to my database.

答案1

得分: 6

以下是翻译好的内容:

  1. 当页面初始加载时,获取并保存您的 sessions 数组中的当前条目数量。假设我们讨论的是要重放的单个页面,这个计数可以保存为 JavaScript 变量 eventCount。最好的做法是从服务器端表格中获取计数,以防出现最后一次关闭页面时未成功保存所有记录的情况。
  2. 安排一个名为 checkEvents 的函数,每隔 N 秒调用一次(您必须决定要调用此函数的频率),使用 Window setInterval 方法。此函数将查看当前事件计数(变量 newCount)并且如果大于当前的 eventCount 值,则可以使用 Navigator sendBeacon 方法向服务器发送 POST 请求,传递自上次调用以来添加的所有事件(即 JSON.stringify(sessions.slice(eventCount, newCount))),当请求完成时,如果 newCount 大于 eventCount,则将 eventCount = newCount。请注意,由于异步的 sendBeacon 请求正在运行,可能会生成新事件,这就是我们使用 newCount 而不是 sessions 数组的当前大小来更新事件计数的原因。
  3. 当页面卸载时,您需要进行最后的 sendBeacon 请求。由于我们知道 beforeunloadunload 事件不可靠,因此我们使用 visibiltychanged 事件(如果浏览器支持的话),当新的可见性状态为 'hidden' 时,我们会更新服务器。文档讨论了触发此事件的用户操作(不仅仅是关闭页面)。但是,如果浏览器不支持此事件,那么将必须使用 pagehide 事件。

Navigator.sendBeacon 方法的文档中没有讨论是否可以有多个并发请求。假设可以(它是一个异步调用),用户在 setInterval 调用期间可能决定导航离开页面或关闭页面,而 sendBeacon 请求正在进行。然后,您将此请求发布到服务器的 URL 应该锁定表格以执行插入,以便任何后续的 POST 到此 URL 将阻塞,直到前一个完成。如果您的表格使用某种序列号作为主键,我还建议向服务器传递传递的第一个事件数组索引的起始值,并且服务器将使用它来显式设置每个插入的事件的序列号。然后,可以在无需锁定整个表格的情况下运行并发更新。

英文:

Here are my thoughts on what you can do to mitigate the unreliableness of the beforeunload event:

  1. When the page is initially loaded get and save the current number of entries in your sessions array. Assuming we are talking about a single page that you wish to replay, this count could be saved as a JavaScript variable eventCount. Better yet would be to get the count from the server-side table in case for some reason not all the events recorded were successfully saved the last time the page was closed.
  2. Arrange for a function checkEvents to be called every N seconds (you must decide how often you want to call this function) using the Window setInterval method. This function will look at the current event count (variable newCount) and if greater than the current eventCount value, then the Navigator sendBeacon method can be used to POST a request to the server passing all the events that have been added since the last call (i.e. JSON.stringify(sessions.slice(eventCount, newCount)) and when the request completes assign eventCount = newCount if newCount is > eventCount. Note that while the asynchronous sendBeacon request is running it is possible that new events have been generated and that's why we update the event count with newCount rather than the current size of the sessions array.
  3. You will need to do a final sendBeacon request when the page is unloaded. Since we know that the beforeunload and unload events are not reliable, then we use the visibiltychanged event (if supported by the browser) and when the new visibility state is 'hidden' we update the server. The documentation discusses those user actions that cause this event to be fired (not just on closing the page). If, however, the browser does not support this event, then the pagehide event will have to be used.

It's not discussed within the documentation for the Navigator.sendBeacon method whether there can be multiple concurrent requests. Assuming there can be (it's an asynchronous call), there is slight possibility the user might decide to navigate away from the page or close it while a sendBeacon request is currently in progress because of the setInterval call. Then the server URL to which you post this request should probably lock the table while doing the insertions so that any subsequent POST to this URL will block until the previous one completes. Another solution I would propose if your table uses some sort of sequence number as the primary key would be to also pass to the server the starting array index of the first event being passed and the server would use that to explicitly set the sequence number for each event inserted. Then concurrent updates could run without having to lock the entire table.

答案2

得分: 2

简介

在这种情况下,你需要评估危险的程度、严重性以及什么样的最低预期可以接受。

问题陈述是,当用户注销或导航离开时,你希望保存一个数组。有多个主题需要涵盖,你可以根据需要将它们结合起来,以获得最佳的混合效果。

注销

这是问题领域中最简单的部分,我强烈建议你将其作为第一个里程碑。在注销时,你需要将数组发送到服务器,然后服务器将保存数组(或其更改)到你的数据库中。

因此,你需要:

  • 能够从数据库中加载数组,将其传递给用户界面,并使其可操作
  • 能够在需要修改数组时进行修改
  • 能够将数组发送到服务器以进行存储

现在,加载部分应该很清楚,可能已经实现了。然而,修改和发送需要一个良好的计划,因为你不希望重复实现相同的功能。

你需要有一个函数,它会在每次修改时运行,另一个会在每次发送时运行。你可能需要将第二个函数设为一个AJAX请求,并在注销点击时触发此AJAX请求,只有在保存完成后才注销。

onbeforeunload

你说这是不可靠的。让我们看看这种不可靠性是如何表现的:链接

基本上,在正常情况下,它会成功运行,因此如果你将数组的发送嵌入其中,它应该会成功发送。然而,的确可能会出现浏览器或操作系统因某种原因崩溃的情况,你也需要处理这种情况。

sessionStorage

你可以将数组存储在sessionStorage中,每当用户在sessionStorage中重新加载页面时,数组就会被同步。但是,当然,这也有一些限制,因为用户可能会使用不同的浏览器,或者他的会话可能已被销毁。后者的问题可以通过localStorage来处理,只要数据不是机密的,但localStorage无法解决前者的问题。

周期性保存

你可以运行setInterval,定期调用你的发送函数(只要有变化需要保存),如果每隔5分钟运行一次,那么最大的数据丢失就是5分钟,这只会在浏览器/操作系统/计算机崩溃时发生,因此这是一种非常罕见且损失最小化的情况。

每次更改都保存

你也可以决定在发生更改时立即将所有更改发送到服务器,这可能会给你的服务器带来一些额外的负担,但如果可以管理,你会获得巨大的好处。

想象一下,当用户在第一个浏览器中登录的同时,还使用第二个浏览器登录。非常希望确保第二个浏览器能够获得在第一个浏览器中进行的任何更改,即使两个会话仍然活动。

WebSocket

你可以创建一个WebSocket连接,维护一个在浏览器和服务器之间的双向通道,允许你立即同步浏览器和服务器,这将允许你创建多个会话,并且每个会话都会实时维护,而你作为用户则无需担心数据同步。

结论

onbeforeunload基本上是可靠的,但在发生崩溃时不起作用。有一些解决方法,也许(很可能)你希望定期保存你的数据,因为在崩溃或其他极端情况下,如果没有间歇性的保存,用户可能最终不得不放弃多个小时的工作成果。因此,请频繁保存,但要注意你服务器的限制。

英文:

Introduction

In such situations, you need to evaluate what the dangers are, how serious are those dangers, and what would be the minimal expectation that would be good-enough.

The problem-statement was that you want to save an array when the user logs out or when he or she navigates away. There are multiple topics we should cover and you can combine them according to your needs in order to get the best possible mix.

logout

This is the easiest part of the problem-space and I strongly suggest that you make this your first milestone. You will need to send your array to your server upon logout and then the server will save the array (or its changes) into your database.

So, you need to:

  • be able to load the array from the database, pass it to the UI, and make it operational
  • be able to modify the array each time when such a modification is needed
  • be able to send the array to your server which will store it

Now, the load part should be pretty clear, it's likely already implemented. However, modification and sending needs a good plan already, because you do not want to implement the same thing twice.

You need to have a function that will run upon each modification and another one that will run upon each sending. And you will likely need to make the second one an AJAX request and trigger this AJAX request upon logout click and only log out when this save has been completed.

onbeforeunload

You said this is unreliable. So, let's see how is this unreliability manifesting: https://stackoverflow.com/questions/16840349/reliability-of-onbeforeunload

Basically, under normal circumstances, it will run successfully, so if you embed the sending of your array into this, then it should be successfully sent. However, it's indeed possible that the browser or the OS would crash for some reason and you need to handle that as well.

sessionStorage

You can store the array in sessionStorage and whenever the user reloads the page while being in sessionStorage, then the array could be synced. But, of course, this comes with limitations, due to the fact that the user may use a different browser or maybe his session was destroyed. The latter problem may be handled by localStorage as long as the data is not confidential, but localStorage will not handle the former problem.

periodic saves

You can run setInterval, which periodically would call your send function (as long as there was some change to save), and, if this runs every 5 minutes, then the maximum data loss is of 5 minutes, which only happens if the browser/OS/comp has crashed, so it would be a very rare situation with minimalized damage.

saving every change

You can also decide to send all changes when they happen to the server, which may create some extra burden on your server, but if it's manageable, then you have huge benefits.

Imagine the case when a user logs in with a second browser while still being logged in with the first one. It is highly desirable to make sure that the second browser will get whatever changes were made in the first browser, even if both sessions are still alive

WebSocket

You could create a WebSocket connection that maintains a duplex channel between the browser and the server, allowing you to sync browser and server instantly and that would allow you to create multiple sessions and each session would be maintained real-time, without having you, as a user to worry about data syncing.

Conclusion

onbeforeunload is pretty much reliable, but in case of crashes, it will not work. There are work-arounds and maybe (probably) you want to save your data periodically, because in the case of crashes or other extreme scenarios, if there are no intermittent saves, the user may end up having to concede the loss of the work of multiple hours. So, save frequently, but pay attention to your server's limits.

答案3

得分: 0

针对这种问题,您应该创建一个数据库架构来存储这些数据,并触发一个函数来接收事件并像通常一样将这些事件保存在数据库中。
对于另一个在关闭网站后的问题,您可以调用一个类似以下的函数:

window.onunload = () => {
  // 保存事件到数据库的函数
}

希望这对您有所帮助。

英文:

For this kind of problem you should make a database schema to store this and fire a function that takes the event and save that events in the database as usual.
And for another problem of the after closing website you may call a function like:

    window.onunload=()=>{
      //function to save the event in the database
}

May this will help you for you.

huangapple
  • 本文由 发表于 2023年6月29日 19:54:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/76580805.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定