Rocket.Chat Iframe integration: browser navigation failure

Created on 22 Sep 2017  路  11Comments  路  Source: RocketChat/Rocket.Chat

Description:

I am integrating Rocket.Chat to our existing web application, which has a large user base. After successfull Rocket.Chat Keycloak integration (SSO), it was decided to inject chat to the existing application using available iframe integration since it allows sending commands for authentication and simplifies Rocket.Chat events handling.

The actual problem is the fact that, iframe integration breaks site navigation, i.e. klicking browser's back button doesn't navigate to the previous page.

Server Setup Information:

  • Version of Rocket.Chat Server: 0.58.2
  • Operating System: Ubuntu
  • Deployment Method(snap/docker/tar/etc): snap
  • Number of Running Instances: 1
  • DB Replicaset Oplog: MongoDB shell version: 3.2.7
  • Node Version:

Steps to Reproduce:

  1. Integrate Rocket.Chat as it is shown in Iframe integration
  2. Navigate in the web application (i.e. openning different menus, pages...)
  3. Click back button of the browser (Chrome, Mozilla)
  4. No matter how many times back button is clicked no navigation is taking place

Expected behavior:

Possibility to navigate back in browser

Actual behavior:

Clicking back button of the browser doesn't open previously open page

uux bug

Most helpful comment

Any update on this issue? I am also having this issue.

All 11 comments

@sampaiodiego can you please investigate this issue?

FWIW, we don't have this issue in our iFrame integration. We are also running 0.58.2

@sampaiodiego, @danieljhochman,

hoping that it can contribute to the understanding and possible solution, following is the short technical description of the actual Rocket.Chat integration:

  • since it is desired to have the chat available throughout the web application, iframe tag is appended to the layout defining component (its a jsp file) as:
<iframe id="rocketChat" src="#" data-src="${requestScope.rocketChatUrl}" frameborder="0"></iframe>
  • a separate .js file defines initialization function and auxilary functionality as follows:
var rocketChat = {
    keyName: 'rocketChatLogin',
    init: function(){
        var iframe = $('#rocketChat');

        if($(iframe).length){
            $(iframe).attr('src', $(iframe).attr('data-src'));
        }

        // event listener for events from rocketchat iframe
        window.addEventListener('message', function(e){
            console.log(e); // todo: remove
            switch(e.data.eventName){
                case 'startup':
                    //rocketChat is loaded
                    rocketChat.startupRocketChat();
                    break;
                case 'unread-changed':
                    // new message
                    // {eventName: "unread-changed", data: 1}
                    // {eventName: "unread-changed", data: ""}
                    rocketChat.notificationChangedRocketChat(e.data.data);
                    break;
                case 'notification':
                    break;
            }
        });
    },
    postToRocketChat: function(externalCommand, service){
        $('#rocketChat')[0].contentWindow.postMessage({
            externalCommand: externalCommand,
            service: service
        }, '*');        
    },
    loginRocketChat: function(){
        window.sessionStorage.setItem(rocketChat.keyName, 'connected'); //add key
        rocketChat.postToRocketChat('call-custom-oauth-login', 'keycloak');
    },
    logoutRocketChat: function(redirectUrl){
        window.sessionStorage.removeItem(rocketChat.keyName);
        rocketChat.postToRocketChat('logout');
        window.location.href = redirectUrl;
    },
    startupRocketChat: function(){
        var isLoggedIn = window.sessionStorage.getItem(rocketChat.keyName);

        if(!isLoggedIn){
            // key not found
            rocketChat.loginRocketChat();
        }

    },
    notificationChangedRocketChat: function(count){
        var notificationElement = $('.notification');
        if(count > 0){
            $(notificationElement).show().text(count);
        } else {
            $(notificationElement).hide().text('0');
        }
    },
    //.....
    //.....
}

@danieljhochman I didn't manage to overcome the navigation failure and curious how have you configured your system. Could you please help/shed some ligth on this issue. Many thanks.

Any update on this issue? I am also having this issue.

this issue is pretty default. When your iframe's url changes it propagates to parent window history. If .go() back far enough you will get to rocket.chat login page and you're already authorized, so it redirects home. You can also go one more step back and that would be your intended back button action, but you have to know how many steps ago your iframe have loaded and there's no way forward. That's what I know. We are all doomed.

Is there some way to prevent meteor and flow router from changing window history?

I could for example postMessage to my window.parent from FlowRouter.triggers.enter([...]) and then in my app try to history.replaceState() but that doesn't make any difference.

Would that be a good idea to conditionally modify FlowRouter methods and call original methods from within FlowRouter.withReplaceState when ?layout=embedded?

The documentation about Keycloak is going to be written. Please follow https://github.com/RocketChat/docs/issues/790 and fell free to help us :)

@engelgabriel could you explain please how Keycloack is going to be helpful in this situation?

I encountered the same issue while integrating the rocketchat and following solution worked for me:

The Issue

1) when initiating the iframe either by writing explicitly in the html or appending it to the DOM through javascript , an new entry of rocketchat's url is pushed in the browser's history stack

2)If you are sending token from your parent application to the rocket chat in the iframe to login, Rocket chat will redirect if login is successful from the root_url to root_url/home creating a new entry in the browser history stack

3)Every time you click a anchor tag (enter a room etc) in the rocket chat (inside iframe) another entry is created and pushed in the browser history stack

Now when the app gets loaded the stack looks like this even if the iframe is hidden by default in the beginning

  • rocketchat_url/home
  • rocketchat_url
  • app_url

now if you press the back button the browser will go to rocketchat_url , the route will find out that a token is already present in the localstorage so it will redirect again to /home, therefore we will keep pressing the back button and it wont work

Solution

We need to avoid creating a new entry in the browser's stack for all the three points I mention above

1 & 2) Instead of giving url to the iframe in the html directly, we can add an empty iframe first, that will create a window object for that iframe , now we can access that window and use location.replace to set the url, this will replace the current entry in the browser history instead of creating a new one , this will work for the first point, for the second point we can directly give the '/home' url so that rocket chat doesnt have to redirect after login, if you are not login /home page will automatically fetch the token from your app if youre using postMessage to send the token.

$('body').append(`<iframe id="chat"></iframe>`);
$('iframe#chat')[0].contentWindow.location.replace(chat._baseUrl+'/home');

3)Adding an event listener on all the anchor tags in rocket chat that will replace the current entry in the history stack with the anchor tag's url so that when click is performed and browser tries to create new entry it finds the same entry already present on top of the stack and does not create a new entry

var anchors = document.getElementsByTagName('a');
        for (var i = 0; i < anchors.length; i++) {
            var anchor = anchors[i];
            anchor.addEventListener('click', function (event) {
                history.replaceState(null, null, anchor.href);
            }, false);
        }

^above code needs to be run inside the iframe window

Hope this will give you enough insight to solve the issue

var anchors = document.getElementsByTagName('a');
        for (var i = 0; i < anchors.length; i++) {
            var anchor = anchors[i];
            anchor.addEventListener('click', function (event) {
                history.replaceState(null, null, anchor.href);
            }, false);
        }

^above code needs to be run inside the iframe window

is that really work to someone?
It doesn't work for me

Was this page helpful?
0 / 5 - 0 ratings

Related issues

engelgabriel picture engelgabriel  路  91Comments

RiusmaX picture RiusmaX  路  81Comments

mikrobyte picture mikrobyte  路  62Comments

AmShaegar13 picture AmShaegar13  路  74Comments

HammyHavoc picture HammyHavoc  路  52Comments