conversejs / converse.js

Web-based XMPP/Jabber chat client written in JavaScript
http://conversejs.org
Mozilla Public License 2.0
3.09k stars 774 forks source link

Broken session after pagereload using prebind #98

Closed qroac closed 10 years ago

qroac commented 10 years ago

I am using converse.js on a webpage using prebind with a BOSH session that gets created on server side and stored in a cookie for reuse after page reload.

On first page call this works fine. The BOSH session is created on server side and Converse gets initiated using the given boshurl, jid, sid and rid.

But after reloading the page and initiating Converse using the same boshurl, jid, sid and rid as used before the reload, the functionality is a bit ... weird Contact and room list gets loaded but all contacts are shown as offline Former opened chats get reopened but not refreshed with new messages and messages written by user are not sent

dazed19 commented 10 years ago

@theWeird I suspect the issue is that the RID is not correct - the Request ID will get incremented by converse everytime it communicates to XMPP server on session and hence using the same stored one a second time will be out of sync - behaviour could well be XMPP server dependant.

jcbrand commented 10 years ago

@theWeird The RID is the request ID. It needs to be unique for each request made. The best way to do this is to increment before passing it to converse.initialize

@dazed19 You're basically right, except that converse.js doesn't do anything with the RID, it passes it straight to the XMPP server.

dazed19 commented 10 years ago

@jcbrand understood - but doesn't strophe, underneath converse, manage the incrementing of RID on each converse/XMPP server interaction. My take is that on first load you are right, increment and pass on, but on browser page loads which (I think) @theWeird is asking about, trying to pass the same RID you started with is unlikely to work, as strophe and XMPP server have moved on. For this to work you'd need to have a way of retrieving the current RID (either server side and passing again or client side and updating cookie).

qroac commented 10 years ago

@jcbrand So the RID gets incremented on each request that is made by converse towards the BOSH server? If I understood this correctly I have to use the last RID +1 for initialisation of Converse after page navigation/reload. But how can I retrieve the last used RID from Converse to increment and use it on next initialisation as dazed19 suggested? I tried to save a reference to "this" in a window scope variable using a callback function in initialisation. But this object seems not to get updated so I can't extract the last RID from there on window unload.

qroac commented 10 years ago

Ok, I found a way to read the RID from connection.rid. Now I can update my boshRid-Cookie on window unload to use it after page reload. Nearly everything works now. The only thing that still doesn't work is the contact list. After page change or reload all contacts are shown as offline. But if I write a message to a contact the "is typing" notice as well as the message itself are sent to the contact. So, why are all contacts shown as offline?

My initialisation:

window.sitechat = false;
    jQuery(document).ready(function() {

        require(['converse'], function (converse) {
            console.log('load jid ' + readCookie('boshJid'));
            console.log('load sid ' + readCookie('boshSid'));
            console.log('load rid ' + readCookie('boshRid'));
            converse.initialize({
                //auto_list_rooms: true,
                bosh_service_url: '{..$bosh..}',
                //fullname: '{..$name..}',
                //hide_muc_server: true,
                //i18n: locales.de, // Refer to ./locale/locales.js to see which locales are supported
                prebind: true,
                jid: readCookie('boshJid'),
                sid: readCookie('boshSid'),
                rid: readCookie('boshRid')
                //show_controlbox_by_default: true
            }, function(){
                window.sitechat = this;
            });
            $(window).unload(function(){
                console.log('unload jid '+sitechat.connection.jid);
                console.log('unload sid '+sitechat.connection.sid);
                console.log('unload rid '+sitechat.connection.rid);
                createCookie('boshRid', sitechat.connection.rid+1);
            })
        });

    });
jcbrand commented 10 years ago

@theWeird Looks like you figured it out.

About the users being offline...

Users' presence statuses are only received once you have broadcasted your presence. This happens as soon as the roster has been received.

See here: https://github.com/jcbrand/converse.js/blob/master/converse.js#L2714

Can you check whether you send out a presence stanza when you reload a page?

If for some reason you don't, then you won't get presence updates from your roster contacts and they will all be shown as offline.

qroac commented 10 years ago

Because I have no idea how to check if the presence stanza is sent I prepared my dev page to use a test xmpp user by default. Could you take a look at it directly? I'll send you the url per XMPP.

jcbrand commented 10 years ago

It's easy. You set debug=True when you initialize converse.js.

Then you check the XML that is being sent around by looking at the developer console (ctrl+shift+j in Chrome and ctrl+shift+k in FF).

converse.js will log all the XML stanzas sent and received. Just check that a stanza is sent from you right after reloading the page.

You can email me the url https://opkode.com/contact.html but I can't promise a quick reply.

qroac commented 10 years ago

Ok, I got the debug output. On first page load there is a presence stanza sent. On first page load the first request is pending until timeout. Afterwards there is no presence stanza sent.

Do you have an idea why this happens? As BOSH server and XMPP service I use an installation of Metronome updated to the latest version.

This is the request that is pending until timeout:

0 request id 1.0 state changed to 1 converse.min.js:163
<body rid=​"9438052138" xmlns=​"http:​/​/​jabber.org/​protocol/​httpbind" sid=​"b14a7055-7d66-4773-8231-718dd74b554f">​
<iq from=​"test%40domain.de" to=​"test%40domain.de" type=​"get" xmlns=​"jabber:​client" id=​"685:​sendIQ">​…​</iq>​
<iq from=​"test%40domain.de" to=​"test%40domain.de" type=​"get" xmlns=​"jabber:​client" id=​"686:​sendIQ">​…​</iq>​
<iq type=​"get" id=​"687:​roster" xmlns=​"jabber:​client">​…​</iq>​
</body>​
 converse.min.js:163
dazed19 commented 10 years ago

@theWeird a(nother) thought.

I'm wondering if, as the unload doesn't disconnect the waiting http request for the SID with the XMPP server, then attaching and sending another request with same SID (second page) - what will the XMPP server do (it now sees two pending HTTP requests for the SID)? - it might send out on first pending request for the SID (your previous connection) and get an error.

This may well be XMPP server specific, but you may need to look at XMPP server logs and/or packet capture on XMPP server to see what it's doing and if/where it's sending responses. So you may need to do a little more on the unload and terminate the http request (without killing the SID)

A further thought - that I've posted on another thread is that - if on each page request a presence is issued then you might want to check how this appears in other users chat and MUC sessions involving this user (both in converse and other XMPP clients). My experience, of forming a new SID on each request (and issue of presence there), was that an MUC room gets bombarded with messages about users presence (consider multiple users/multiple page requests). You may not be intending to use/allow MUC rooms in your usage, but I flag this so that you (and others) can check that first.

This was main reason why I settled on running converse in a new tab rather than within web app tab. Of course if there is a way of getting your approach here to work without all the presence output then then I'm still very interesting.

jcbrand commented 10 years ago

@theWeird Ok, in the request that timeouts, you have this:

<iq type=​"get" id=​"687:​roster" xmlns=​"jabber:​client">​…​</iq>​

Converse.js makes a roster request to the XMPP server, and only after it has received the roster from the XMPP server will it broadcast presence.

So, if you for some reason are not getting the roster back from the XMPP server, converse.js won't broadcast the user's presence, as a result you won't receive your roster contacts' presences and they will therefore all appear as offline.

So the challenge is to figure out whether you really don't get a roster response from the XMPP server upon subsequent page loads and if you don't, why not.

codebreaker90 commented 10 years ago

Still I do think that if converse return an current RID, The pre-bind function should be able to continue with its old session without the need to established a new one because I tried tracking it before with converse.

Establishing a new session every time reload or navigate to another page or a new tab should not be a problem as long as the session and RID is valid. But yes, there is still some possibility that there will be some errors but it is good to give it a try.

And a new session keep established in the server but did not disconnecting them which I worries that the server will get loaded and keep on work making new session.

jcbrand commented 10 years ago

@theWeird Any update on this?

babu3009 commented 10 years ago

@theWeird and @jcbrand I too share the same problem, does anyone of you know the answer ?

@jcbrand is there a way to save the status of each user's status into array inside a cookie and read it back, and update the array everytime presence is updated from server.

shdk commented 10 years ago

Same problem here.

Any ideas how to solve this problem?

mbjolin commented 10 years ago

I had same problem with cookie hack while reload page. I use xmpp server(OpenFire), it's very peaky with requestId Number, it can be +1 or +2. Each communication between client and server is increase by 1. So, I handle all event like it.

function incrementRID (){ if(parseInt(converse.getRID()) != parseInt($.cookie("RID"))){ $.cookie("RID", parseInt(converse.getRID())+1); } } converse.on('initialized', function () { incrementRID();}); converse.on('ready', function () { incrementRID(); }); ...

xavier83ar commented 10 years ago

I've read all documentation related to this issue, I've read Jack Moffitt's example written in python, I've read almost every related question from stackoverflow, and finally I've also done many tests and I can not get this to work.

I'm using https://github.com/candy-chat/xmpp-prebind-php/ for pre-binding from php, and ejabberd with bosh module enabled as xmpp server.

On first page load everything goes right, php handles sesion start and pass sid and rid to converse initialize method. I've managed to get last rid from js with cookies to use in php for further page requests. The issue is that on every next request, when php only pass sid and rid (without handling sesion start), converse initialize logged in, but with empty contact list, also it doesn't "remember" widget's state like if it was minimized.

So, is actually possible to use converse js with server-side authentication, prebinding and single session support? Could someone achieve this?

Nagesh29 commented 8 years ago

I am also facing the same issue. Everything works perfect when I invoke initialize function on page load. Connected event is fired and following functionalities works as expected converse.contacts.get(), converse.archive.query(options, callback, errback), message send, message on received event, etc. I am getting connection timeout after one minute and then nothing is working.

prajwalpoojari9876 commented 6 years ago

@jcbrand Hi, we were facing the same issue that the chat was not working after page reload. We made a solution for this, we removed authentication: prebind, instead we are using authentication: login only. And on page unload we logout the user. So on refresh page its login again. The chat runs smoothly,

var sitechat;

converse.plugins.add('myplugin', {
    initialize: function () {
        var _converse = this._converse;
        _converse.on('connected', function () {
            sitechat = _converse;
        });
    }
});

$(window).unload(function(){
    sitechat.api.user.logout();
});