Problems Inserting a Chatbot and The ‘All Too Obvious’ Solution

Georgy Marchuk
Pipedrive R&D Blog
Published in
11 min readApr 7, 2020

It’s a couple months into the beginning of the year again, and I’ve been subconsciously evaluating the previous year. As luck would have it, the beginning of the year marked almost a year since I’ve joined a team at Pipedrive, which made the evaluation pretty straightforward for me.

In this piece, I’ll go through the story of developing a global product from the very beginning. The story talks about some unexpected problems encountered while developing a chatbot, and through that, also a case study on the current state of the web.

Before we start, let’s talk about the product a bit as it’s an important aspect to the decisions made on the way. The product, LeadBooster, is one of the newest additional features found in Pipedrive and as mentioned before, it’s a chatbot which our customers can include on their website. LeadBooster interacts with the website users in a way that that can be customized by the website owner and if everything goes well, the chatbot saves the contact information as a potential customer. If you’re reading this post, likely you’ve seen a chatbot before on the web so there’s no need to go into any particularly deep explanation of how it works.

That said, here’s a demo of the LeadBooster chatbot for anyone curious.

Let me add a few important technical aspects here. Since this is a service that should be globally available for everyone, we officially support browsers down to Internet Explorer 11. The chatbot is injected into a website with JavaScript snippet, just like any other service or analytics that you’re used to.

<script>
window.pipedriveLeadboosterConfig = {
base: 'leadbooster-chat.pipedrive.com',
companyId: ...,
playbookUuid: '...',
version: 2
};
</script>
<script src="https://leadbooster-chat.pipedrive.com/assets/loader.js" async></script>

Styling, Styling, and Styling Again

There are many lessons learned, but I can reveal the first one right at the beginning, and that’s a somewhat naive thinking of how “clean” the web is in 2020. It all started with a simple question — how to style a module injected into websites? Since the environment is virtually uncontrollable from our side, the decisions we were making had to be well thought out, or at least so we thought.

The idea was simple: DOM elements of the injected chatbot would be placed into their own wrapper div element, placed right at the root of the website — the body element. Doing this would allow us to avoid most of the non-global styles from outside by default. Yay!

Of course, most websites include some kind of style reset which set default styles of elements, so we made sure we to take that into account. The chatbot included its own scoped style reset that would globally set styles of elements within our wrapper and those styles could be changed for specific components at will. Some style collisions were surely expected, but since the product was in the early beta at the time, there was no harm in going for try → fail → learn approach.

First Issues

We had around 3,500 installations in the beta phase, which is a nice number from a business perspective, but about a perfect number to have a wide user base for testing the tool out in the wild.

It didn’t take much time for the first issues to appear but as mentioned, that was the plan. This rabbit hole eventually turned out to be much deeper than anyone originally expected, but let’s walk through all the steps one by one.

Most of the issues came from the different global styles used on the websites. The main problem is the unnecessary specific CSS selectors, something like the following example, where the body doesn’t have to be there, probably clears things out.

body button {
color: red;
}

Surely, many readers have met this problem before regardless the circumstances, so I won’t go deeper into the topic of CSS specificity. This topic has been discussed many times over after all and there is even a CSS specificity calculator.

CSS selector specificity

Luckily this problem can be quickly identified by the same try → fail → learn technique. Injecting the chatbot into commonly used websites like popular WordPress templates has shown most of the issues, where the majority of it can be resolved by increasing the specificity of the chatbot selectors.

Transforming all selectors with a prefix was a go-to fix that resolved a big chunk of bugs related to style conflicts. Plus, it can be easily automatized with pretty much any CSS pre-processor or CSS in JS solution.

html body #pipedrive-chat-holder { ... }

The Perils of Important

The next wave of problems didn’t take too long to show up, and as the headline suggests, the trouble was something like the following:

button {
color: red !important;
}

The infamous and feared !important. Anyone reading this article likely knows that this CSS feature is mainly meant for overwriting values set on inline on elements by some uncontrolled source, and is not to be used for simply increasing the specificity of selectors. Sure, there are many places where going for !important is a valid, well thought out solution, but let’s face it, we’ve all used it once or twice in that “bad way”. I know I have.

!important

The problem with !important is simple — once it’s introduced, there’s no way around it. You can’t fight !imporant with your usual CSS toolset. It’s like a magical and immortal creature — it cannot be killed. The only thing that can beat this monster is yet another !imporant.

Everyone is likely to make this mistake here or there, but what makes the issue worse is that everyone makes this mistake in a different place, which essentially means we needed to account for all of the different places.

The issues with !important were less common, but started showing up from the users later with reports of “broken chatbot”.

Since !important was usually applied to some more specific HTML tags like button, unfortunate side effects emerged as well. This issue practically blocked the progress of the “good guy” frontend developers in our team who wanted to make the chatbot more accessible by introducing a semantic HTML5 syntax.

To sum it all up, we were left in a very unpleasant situation where we found ourselves handling code filled with !important — not because we didn’t know better, but because our customers were inadvertently forcing us to.

JavaScript Collisions

All the problems above are real-world issues that needed to be solved, but at the same time were issues that were somewhat expected to come up. What was unexpected was a collision of styles for elements sharing one document.

What no one was anticipating was a collision of JavaScript code. Now don’t get me wrong, I know there are many opportunities to make a mistake in JavaScript, but given a classic toolbox of 2020 where bundlers automatically enclose any code into its own scope and all the linters go crazy when a mistake is made — most dangerous code will never make it to production. I think this particular setup actually makes it quite hard to make mistakes, but then again, that might be just a naive assumption.

There is another factor to consider however— these tools don’t have to be used by whoever develops the website that the chatbot is injected to.

There was a perfect real-world example we faced: a user reported that the chatbot wouldn’t load on their website and the report luckily included a helpful console error familiar to most of us.

Uncaught TypeError: fetch is not a function

The conclusion was made in seconds — the user is accessing the website in IE 11 or lower and we’re missing a polyfill.

Polyfill is a piece of code that implements features in the browsers that don’t support it. Commonly, a polyfill will check whether the native implementation of the feature is present and if not, implement all the functionality agreed upon by the set standards.

I’ll get to polyfills once again later, but a quick investigation revealed that there was no such issue and the mystery continued with the fact that the error could be seen in the latest Chrome as well. This suggested that the missing polyfill is likely not the problem we are dealing with.

Since fetch wasn’t a function, it had to be something else. You would think that null is what would be found, but that wasn’t the case this time around. A quick check of what is the fetch global variable on the page showed us the following:

window.fetch  // window.fetch = 4

To this day, I’m still wondering what was the origin of this definition. Did the author pick a very unfortunate name for a global variable that holds a number of times loop repeats? Perhaps it was a favorite number that was just saved in the wrong place? Maybe just a simple mistake in the declaration that led to variable going global? Unfortunately, we’ll never know, but a quick chat with support resolved the issue for the customer.

Frameworks

Now we can move on to the last issue we have faced, but before we do this, let’s talk a little about frontend frameworks. Developing a framework for the web has some advantages over developing a library that people would use. The advantage is simple — framework takes care of everything and you don’t have to take other third party code into account, at least to some extent — it’s essentially an enclosed system that can be designed as a whole. Angular is a great example of such a framework and this actually leads us to our next issue. After testing with thousands of customers, we got our first customer trying to install LeadBooster on an Angular website. We expected this to happen way sooner, but I guess there aren’t as many brave man building websites with Angular as we expected.

As it usually happens, everything started with the first customer complaints. This time however, it was different. Unlike the other reports, this customer stated that LeadBooster broke the website and not the other way around. Being the team of careful developers that we are, we were quite sure couldn’t be the case. All of the chatbot code is structured in a way so that it doesn’t affect any other code on a page.

Like anyone in our position, we took such a complaint seriously and immediately started to research the issue. We installed a clean installation of Angular, made a few routes and started testing. One thing led to another and guess what? The website broke. It wasn’t broke in the way that a problem was immediately noticeable, but there was still a valid problem.

You see, as previously mentioned, LeadBooster needs to work everywhere including less-fortunate browsers like Internet Explorer 11. This means our code needs to include a few polyfills.

Let’s get back to the problem. It turns out that Angular, being a framework that doesn’t need to consider third party code, overwrites a global Promise definition. Unlike polyfills, Angular implements a custom Promise in any browser. This might not be an issue in most cases, but this non-native implementation of Promise doesn’t go well with Promise included by default from Babel. Perhaps you know where I’m going with this…

Overwriting Promise Definition

The browser has a native implementation of Promise and it is overwritten by any custom implementation for any Angular website. Eventually, once LeadBooster has loaded, it overwrites the Angular Promise which then breaks the website altogether. In a sense, it reminds me of those documentaries about the series of unfortunate events leading up to a terrifying catastrophe.

Whether Angular overwriting native global values is the correct approach is definitely up for discussion, but let’s skip that for now. Let’s focus on the problem instead. We never expected the creators of a website to count on a non-standard implementation of global objects, and that was a mistake on our end. We needed a solution, and we needed it fast.

The Ultimate Solution

No matter the issues we’ve faced, whether it was the specificity of styles or a collision in JavaScript code, it has culminated to one ultimate solution. This solution was clear from the beginning, but since it adds its own overhead, the initial idea was to avoid it if possible. Many of you have probably figured out the answer to all the troubles by now based on the article title — IFrame.

IFrame is in many ways, a product of the past. Its original intention is also significantly distant from the way we use it now and yet, it’s the only solution that can cover all the problems arising from a very much current trend — chat windows on a page.

IFrame, unlike any other HTML tag or JavaScript code that one can stumble upon in a browser, creates a whole new context, whole new document, and whole new window.

There are of course limitations brought on by IFrame. It makes usage of CSS media queries within the IFrame virtually impossible since media queries are evaluated against the IFrame window object, not the main one. Also, it’s size and position needs to be controlled manually to adjust for anything that needs to be displayed within.

There was also one argument against the IFrame that surfaced during the initial research of the market, and the argument would probably surprise most people. There are other products within the Pipedrive world that are already using IFrame, so the idea isn’t new at all. However, as it turns out, our customers don’t think IFrame is cool anymore, and they don’t want to use it on their website unless they need to, but that’s just a fun fact more than anything else.

LeadBooster has now been using IFrame for a few months, and it seems to have solved most of our problems. The transition was also surprisingly smooth, although I should mention a few of the customer complaints we have received. You see, some customers had already started exploiting the benevolent nature of our solution before the change. Some started introducing custom styles for the chatbot, and some even implemented custom solutions for analytics. In a way, it’s amazing (and impressive) what people can manage with a little freedom. Unfortunately, all of this DIY functionality doesn’t work anymore, but since LeadBooster is a SaaS, receiving frequent updates that would sooner or later break any custom functionality, I think it’s for the best.

Conclusion

When designing a code that needs to run in an unknown environment, one shouldn’t assume anything. Even when everyone involved has the best intentions, there are just too many things that can go wrong, and they will eventually.

On the contrary, an IFrame, being in a way an outdated tool, can be incredibly helpful and it’s essentially the only solution to certain problems existing on the web today. There are surely a list of disadvantages for IFrame, but the point is — all of them can be solved. What IFrame brings is a definite separation of code, and that’s not possible in any other way at this time. I would never anticipate being the one saying it, but IFrame still has a useful position place in 2020.

In case anyone is interested, here’s a demo of LeadBooster chatbot.

--

--