8

在我的聊天应用程序中,我有注销按钮,它工作正常。

现在,当我关闭浏览器窗口时,我也需要注销应用程序。我怎样才能做到这一点......

提前致谢...

4

11 回答 11

12

对于客户端,没有确切的方法可以做到这一点。退出页面时不会触发任何事件。它应该通过服务器上的 Session End 事件来完成。

您可以尝试使用 onbeforeunload 或 unload,但竞争条件会阻止这种情况发生。并且它们不会因为浏览器崩溃、失去互联网连接等而触发。

于 2012-11-15T15:23:21.997 回答
7

我最近在我的 angularJS 应用程序中处理了这个问题 - 主要问题是,如果你刷新,我不想让你注销,但如果你关闭选项卡,我确实想注销.. 带有 onbeforeunload/onunload 的 Ajax 请求不是保证等待响应,所以这是我的解决方案:

我在登录时设置了一个 sessionStorage cookie,它只是一个布尔值 - 当我收到登录响应时设置为 true

sessionStorage.setItem('activeSession', 'true');

显然,在注销时,我们将此标志设置为 false

当控制器初始化或使用 window.onload (在我的 app.js 文件中)时 - 我检查这个 activeSession bool .. 如果它是假的,我有这个小 if 语句 - 如果满足条件我调用我的注销方法ONLOAD代替卸载的

   var activeSession = sessionStorage.activeSession;
    if (sessionStorage.loggedOutOnAuth) {
        console.log('Logged out due to expiry already')
    }
    else if (!activeSession) {
        sessionStorage.loggedOutOnAuth = true;
        _logout()
    }

基本上,“loggedOutAuth”布尔值让我知道,由于 sessionStorage 中没有 activeSession,我刚刚在页面加载时让你过期,所以你不会陷入循环

这对我来说是一个很好的解决方案,因为我不想实现心跳/websocket

于 2016-07-19T18:54:42.183 回答
6

将您的注销代码添加到onunload事件。

window.onunload = function () {
    //logout code here...
}

在 JQuery 中,您可以使用.unload()函数。请记住,您没有太多时间,因此您可以发送 Ajax 请求,但结果可能无法到达客户端。

另一个技巧是打开一个小的新窗口并在那里处理注销。

window.open("logout url","log out","height=10,width=10,location=no,menubar=no,status=no,titlebar=no,toolbar=no",true);

如果要禁用关闭窗口(或至少警告用户),可以使用以下代码:

window.onbeforeunload = function(event) {
    //if you return anything but null, it will warn the user.
    //optionally you can return a string which most browsers show to the user as the warning message.
    return true;
}

另一个技巧是每隔几秒钟就对客户端进行一次 ping 操作。如果没有回复,则假定用户已关闭窗口、浏览器已崩溃或存在网络问题导致聊天会话结束。在客户端,如果你没有收到这个 ping 包,你可以假设网络连接或服务器有问题,你可以显示注销警告(并且可以选择让用户重新登录)。

于 2012-11-15T15:22:45.727 回答
1

一些网站正在使用以下脚本来检测窗口是否关闭。

if(window.screenTop > 10000)
alert("Window is closed");
else
alert("Window stillOpen");

您需要添加正确的操作而不是alert()

也看看这里- 我认为这是你需要检测窗户关闭的东西

于 2012-11-15T15:22:59.513 回答
1

我得到了解决方案,

window.onunload = function () {
    //logout code here...
}

感谢所有支持我的人...

于 2012-11-16T08:37:48.707 回答
1

另一种方法是某种“keepalive”:浏览器页面每分钟左右用一个小的 ajax 请求“ping”服务器。如果服务器没有收到常规 ping,则会话将关闭并且不能再使用。

作为优化,如果我们在此期间向服务器发出另一个请求,则可以跳过 ping。

好处:

  • 仍然可以打开多个窗口
  • F5/刷新没问题
  • 可以向服务器提供一些使用统计信息

缺点:

  • 当窗口关闭时,用户退出前会有延迟
  • 使用少量网络带宽
  • 服务器上的额外负载
  • 用户可能会担心页面不断“打电话回家”
  • 更难实施

我从来没有在网络应用程序中真正做到过这一点,我不确定我是否会这样做;只是把它作为一种选择。对于聊天应用程序来说,这似乎是一个不错的选择,服务器确实需要知道你是否还在。

除了轮询/ping,另一种可能性是在页面打开时保持“长时间运行的请求”打开。聊天应用程序需要一些这样的套接字来接收新消息和通知。如果页面关闭,则套接字也关闭,服务器可以注意到它已关闭。然后它会等待客户端建立新套接字的短暂时间,如果没有,我们假设页面已关闭并删除会话。这将需要在服务器上安装一些稍微不寻常的软件。

于 2015-12-09T15:38:18.037 回答
0

我在这里遇到了这个问题,我有一个不同的解决方案:

    checkSessionTime();
    $interval(checkSessionTime, 2000);
    function checkSessionTime() {
        var now = (new Date()).getTime();
        if (!$localStorage.lastPing) {
            $localStorage.lastPing = now;
        }

        if ($localStorage.lastPing < now - 5000) {
            $localStorage.lastPing = undefined;
            AuthService.logout();
        } else {
            $localStorage.lastPing = now;
        }
    }

我喜欢这个解决方案,因为它不会增加 ping 服务器的开销,也不会依赖窗口卸载事件。这段代码被放在了$app.run.

我正在使用带有 JWT 身份验证的角度,这种方式让我注销只是意味着摆脱身份验证令牌。但是,如果您需要完成会话服务器端,您可以构建 Auth 服务以在完成会话时执行一次 ping,而不是继续 ping 以保持会话处于活动状态。

这个解决方案解决了我的问题,因为我的意图只是为了防止不需要的用户在关闭选项卡并离开 PC 时访问他们的帐户。

于 2016-08-27T03:26:42.260 回答
0

经过大量搜索后,我在 javascript 中编写了以下自定义代码,并在 c# 中编写了用于会话终止的服务器端代码。

如果同一网站在多个选项卡中打开,则下面的代码将被扩展,因此会话一直存在,直到网站的一个选项卡打开

//Define global varible 
var isCloseWindow = false;

 //Jquery page load function to register the events

 $(function () {
        //function for onbeforeuload
        window.onbeforeunload = ConfirmLeave;
        //function for onload
        window.onload = ConfirmEnter;

        //mouseover for div which spans the whole browser client area 
        $("div").on('mouseover', (function () {
        //for postback from the page make isCloseWindow global varible to false
            isCloseWindow = false;
        }));
        //mouseout event
        $("div").on('mouseout', (function () {
     //for event raised from tabclose,browserclose etc. the page make isCloseWindow global varible to false
            isCloseWindow = true;
        }));
    });
     //Key board events to track  the  browser tab or browser closed by ctrl+w or alt+f4 key combination
    $(document).keydown(function (e) {
        if (e.key.toUpperCase() == "CONTROL") {
            debugger;
            isCloseWindow = true;
        }
        else if (e.key.toUpperCase() == "ALT") {
            debugger;
            isCloseWindow = true;
        }
        else {
            debugger;
            isCloseWindow = false;
        }
    });

    function ConfirmEnter(event) {
        if (localStorage.getItem("IsPostBack") == null || localStorage.getItem("IsPostBack") == "N") {
            if (localStorage.getItem("tabCounter") == null || Number(localStorage.getItem("tabCounter")) == 0) {
                //cookie is not present
                localStorage.setItem('tabCounter', 1);

            } else {
                localStorage.setItem('tabCounter', Number(localStorage.getItem('tabCounter')) + 1);
            }
        }
        localStorage.setItem("IsPostBack", "N");
    }
    function ConfirmLeave(event) {
        if (event.target.activeElement.innerText == "LOGOUT") {
            localStorage.setItem('tabCounter', 0);
            localStorage.setItem("IsPostBack", "N");
        } else {
            localStorage.setItem("IsPostBack", "Y");
        }
        if ((Number(localStorage.getItem('tabCounter')) == 1 && isCloseWindow == true)) {
            localStorage.setItem('tabCounter', 0);
            localStorage.setItem("IsPostBack", "N");
            **Call Web Method Kill_Session using jquery ajax call**
        } else if (Number(localStorage.getItem('tabCounter')) > 1 && isCloseWindow == true) {
            localStorage.setItem('tabCounter', Number(localStorage.getItem('tabCounter')) - 1);
        }
    }

 //C# server side WebMethod
 [WebMethod]
  public static void Kill_Session()
    {
        HttpContext.Current.Session.Abandon();
    }
于 2016-12-16T05:27:48.543 回答
0

对于这个问题,我尝试了 2 个解决方案:window.onbeforeunload事件和sessionStorage

  • 由于 window.onbeforeunload不仅用于关闭浏览器,还用于重定向、标签刷新、新标签,因此它不是一个强大的解决方案。也有事件没有发生的情况:通过命令行关闭浏览器,关闭计算机

  • 我切换到使用sessionStorage. 当用户登录时,我将 sessionStorage 变量设置为“true”;当应用程序加载时,我会检查这个变量是否存在,否则我会强制用户登录。但是我需要跨选项卡共享 sessionStorage 变量,这样用户在打开时不会被迫登录在同一个浏览器实例中的新选项卡中,我能够通过利用存储事件来做到这一点;一个很好的例子可以在这里找到

于 2019-07-05T22:04:46.217 回答
0
tabOrBrowserStillAliveInterval;

constructor() {
  // system should logout if the browser or last opened tab was closed (in 15sec after closing)
  if (this.wasBrowserOrTabClosedAfterSignin()) {
    this.logOut();
  }

  // every 15sec update browserOrTabActiveTimestamp property with new timestamp
  this.setBrowserOrTabActiveTimestamp(new Date());
  this.tabOrBrowserStillAliveInterval = setInterval(() => {
    this.setBrowserOrTabActiveTimestamp(new Date());
  }, 15000);
}

signin() {
  // ...
  this.setBrowserOrTabActiveTimestamp(new Date());
}

setBrowserOrTabActiveTimestamp(timeStamp: Date) {
  localStorage.setItem(
    'browserOrTabActiveSessionTimestamp',
    `${timeStamp.getTime()}`
  );
}

wasBrowserOrTabClosedAfterSignin(): boolean {
  const value = localStorage.getItem('browserOrTabActiveSessionTimestamp');

  const lastTrackedTimeStampWhenAppWasAlive = value
    ? new Date(Number(value))
    : null;
  const currentTimestamp = new Date();
  const differenceInSec = moment(currentTimestamp).diff(
    moment(lastTrackedTimeStampWhenAppWasAlive),
    'seconds'
  );

  // if difference between current timestamp and last tracked timestamp when app was alive
  // is more than 15sec (if user close browser or all opened *your app* tabs more than 15sec ago)
  return !!lastTrackedTimeStampWhenAppWasAlive && differenceInSec > 15;
}

工作原理:如果用户关闭浏览器或关闭所有打开的应用程序选项卡,则在 15 秒超时后 - 将触发注销。

  • 它适用于打开多个窗口
  • 服务器上没有额外的负载
  • F5/刷新没问题

浏览器限制是我们在注销前需要 15 秒超时的原因。由于浏览器无法区分这些情况:浏览器关闭、选项卡关闭和选项卡刷新。所有这些动作都被浏览器视为相同的动作。所以 15 秒超时就像一种解决方法,只捕获浏览器关闭或关闭所有打开的应用程序选项卡(并跳过刷新/F5)。

于 2021-02-22T10:56:35.563 回答
0

我最初在这里发布了这个,但为了连续性,我将在这里重新发布。

浏览器进行了更新,以便在离开应用程序时更好地吸引用户。事件 'visibilitychange' 可让您在页面从另一个选项卡中隐藏或关闭时进行跟踪。您可以跟踪文档可见性状态。属性 document.visibilityState 将返回当前状态。您将需要跟踪登录和退出,但它更接近目标。

更新的浏览器支持这一点,但 safari(我们知道)从不符合标准。您可以使用 'pageshow' 和 'pagehide' 在 safari 中工作。

您甚至可以使用诸如 sendBeacon 之类的新 API 在选项卡关闭且不应期待响应时向服务器发送单向请求。

我构建了一个用于跟踪它的类的快速端口。我不得不删除框架中的一些调用,所以它可能是错误的,但这应该让你开始。

export class UserLoginStatus
{
    /**
     * This will add the events and sign the user in.
     */
    constructor()
    {
        this.addEvents();
        this.signIn();
    }

    /**
     * This will check if the browser is safari. 
     * 
     * @returns {bool}
     */
    isSafari()
    {
        if(navigator && /Safari/.test(navigator.userAgent) && /Chrome/.test(navigator.userAgent))
        {
            return (/Google Inc/.test(navigator.vendor) === false);
        }
        return false;
    }

    /**
     * This will setup the events array by browser.
     * 
     * @returns {array}
     */
    setupEvents()
    {
        let events = [
            ['visibilitychange', document, () =>
            {
                if (document.visibilityState === 'visible')
                {
                    this.signIn();
                    return;
                }

                this.signOut();
            }]
        ];

        // we need to setup events for safari
        if(this.isSafari())
        {
            events.push(['pageshow', window, (e) =>
            {
                if(e.persisted === false)
                {
                    this.signIn();
                }
            }]);

            events.push(['pagehide', window, (e) =>
            {
                if(e.persisted === false)
                {
                    this.signOut();
                }
            }]);
        }

        return events;
    }

    /**
     * This will add the events.
     */
    addEvents()
    {
        let events = this.setupEvents();
        if(!events || events.length < 1)
        {
            return;
        }

        for(var i = 0, length = events.length; i < length; i++)
        {
            var event = events[i];
            if(!event)
            {
                continue;
            }

            event[1].addEventListener(event[0], event[3]);
        }
    }

    /**
     * 
     * @param {string} url 
     * @param {string} params 
     */
    async fetch(url, params)
    {
        await fetch(url, 
        {
            method: 'POST',
            body: JSON.stringify(params)
        });
    }

    /**
     * This will sign in the user.
     */
    signIn()
    {
        // user is the app
        const url = '/auth/login';
        let params = 'userId=' + data.userId;

        this.fetch(url, params);
    }

    /**
     * This will sign out the user.
     */
    signOut()
    {
        // user is leaving the app

        const url = '/auth/logout';
        let params = 'userId=' + data.userId;

        if(!('sendBeacon' in window.navigator))
        {
            // normal ajax request here
            this.fetch(url, params);
            return;
        }

        // use a beacon for a more modern request the does not return a response
        navigator.sendBeacon(url, new URLSearchParams(params));
    }
}
于 2021-11-13T00:51:57.347 回答