Using Intercom with wire:navigate cover image

Using Intercom with wire:navigate

skeemer • September 19, 2023

livewire

Problem

While improving a site to use wire:navigate?, I was seeing errors in the javascript console when navigating that weren't impeding the navigation.

VM842 frame-modern.46eb31cc.js:1 Uncaught TypeError: Cannot read properties of null (reading 'document')
    at Object.read (VM842 frame-modern.46eb31cc.js:1:236391)
    at ra (VM842 frame-modern.46eb31cc.js:1:400563)
    at Session.createOrUpdateUser (VM842 frame-modern.46eb31cc.js:1:402558)
    at App.createOrUpdateUser (VM842 frame-modern.46eb31cc.js:1:429677)
    at Object.update (VM842 frame-modern.46eb31cc.js:1:434363)
    at VM842 frame-modern.46eb31cc.js:1:436689
    at <anonymous>:3:108
    at <anonymous>:3:606
    at swapCurrentPageWithNewHtml (livewire.js?id=28cda9ab:5928:19)
    at livewire.js?id=28cda9ab:6092:11

I finally realized that Intercom was disappearing from the page and from the dom, so of course it couldn't be found.

I initially looked into wire:persist/@persist. That failed when I found that Intercom didn't support adding the new elements anywhere but the <body>. That didn't completely kill the solution as I could add it with javascript, except there is also no event created when Intercom is initialized. This could be dealt with by timeouts or some Alpine.js magic, but not really a great solution.

Fortunately, Intercom had to fix this problem for their intercom-rails package. A similar technology, Turbo, had the same issue and was solved by firing events off to Intercom to handle the page changes. The solution provided is not documented at all, but I'm betting that it won't disappear as this is how their official package handles it.

Solution

This basic solution is just adding a couple listeners for wire:navigate events and fire off the events to Intercom.

document.addEventListener('livewire:navigating', () =>
    document.dispatchEvent(new Event('page:before-change')
))
document.addEventListener('livewire:navigated', () =>
    document.dispatchEvent(new Event('page:change')
))

This is how I'm using it. I've created the file, config/intercom.php, and I'm putting this in the <head> because Livewire doesn't reload anything put there.

<head>
    ...

    @if (config('intercom.app_id'))
        <script>
            // From Intercom docs
            const APP_ID = "{{ config('intercom.app_id') }}";
            (function(){var w=window;var ic=w.Intercom;if(typeof ic==="function"){ic('reattach_activator');ic('update',w.intercomSettings);}else{var d=document;var i=function(){i.c(arguments);};i.q=[];i.c=function(args){i.q.push(args);};w.Intercom=i;var l=function(){var s=d.createElement('script');s.type='text/javascript';s.async=true;s.src='https://widget.intercom.io/widget/' + APP_ID;var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s, x);};if(document.readyState==='complete'){l();}else if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})();

            // Fire events for changing pages
            document.addEventListener('livewire:navigating', () => document.dispatchEvent(new Event('page:before-change')))
            document.addEventListener('livewire:navigated', () => document.dispatchEvent(new Event('page:change')))
        </script>
    @endif
</head>
<?php

return [
    'app_id' => env('INTERCOM_APP_ID', ''),
    'id_secret' => env('INTERCOM_ID_SECRET', ''),
];