#View transition and 3rd party scripts

112 messages ยท Page 1 of 1 (latest)

verbal jungle
#

Hi there! I'm using https://checkoutpage.co/ and they provide a script that should be used like so:

<script type="text/javascript" src="https://checkoutpage.co/js/overlay.js" defer></script>

This script adds an iframe at the end of the body. However, when navigating to a new page, it doesn't recreate the iframe. What's the recommended way of handling this?

Our no-code checkout page builder allows you to easily make sales from your website or at any point in your sales funnel.

soft rainBOT
#
No-one around right now?

It looks like no-one has responded to your question yet. People might not be available right now or donโ€™t know how to answer your question. Want an answer while you wait? Try asking our experimental bot in #1095492539085230272.

robust comet
verbal jungle
#

I checked this part but I think it can bother many astro users id they need to go into 3rd party scripts internals to see what's been set etc

#

wdyt?

jaunty plover
#

Running into this same issue with OneTrust and AudioEye scripts. The only option that I've gotten to work is not using view transitions, but would really like to find a solution.

robust comet
#

Hi @jaunty plover, how is your script integrated?

jaunty plover
#

Hi @robust comet I've gone down a variety of paths with no luck. The issue is that a script like AudioEye injects a variety of scripts and markup into the body, and when you naviate to a new page, the script doesn't reinitialize.

I've tried astro:page-load and even putting all of the injected scripts and markup into a component with transition:persist, but no luck. Turning off View Transitions works, but am trying to avoid that route.

robust comet
#

You need to find a way to reinitialize the script once the new page is loaded. Do you have a minimal public repository with the issue so I could take a look?

jaunty plover
#

I don't currently have anything public facing, but the script is pretty straightforward.

<script type="text/javascript">!function() {
    var b = function() {
        window.__AudioEyeSiteHash = โ€œAUDIOEYE_ID;
        var a = document.createElement("script");
        a.src = "https://wsmcdn.audioeye.com/aem.js";
        a.type = "text/javascript";
        a.setAttribute("async", "");
        document.getElementsByTagName("body")[0].appendChild(a)
    };
    "complete" !== document.readyState ? window.addEventListener ? window.addEventListener("load", b) : window.attachEvent && window.attachEvent("onload", b) : b()
}();
</script>
robust comet
#

@jaunty plover I would try the following:

<script>
(function () {
  const b = function () {
    ... like in your original
  };
  document.addEventListener("astro:after-swap", b);
  b()
})();
</script>

i.e.

  • script tag w/o any attributes to get it hoisted by astro
  • event listener for astro:after-swap on the document that is called after every transition
  • followed by a direct call to b() for the first load
    (+ some syntax correction to the iife part to make the parser happy)
jaunty plover
#

Appreciate the suggestion. I played around with it and some variations, but still no luck. There are no errors, it works on initial page visit, the script gets re-injected after changing pages, but won't re-initialize.

robust comet
#

Sorry, seems like the script just comes back on the second start. Last desperate trial: just before window.__AudioEyeSiteHash = ... insert the line window.__audioEyeInitialized = 0. If this doesnt help, Im afraid I`m running out of ideas.

jaunty plover
#

It's like a house of cards. Adding that gets one step further and results in https://wsv3cdn.audioeye.com/bootstrap.js also being loaded onto every page visited, but that then isn't reinitialized.

verbal jungle
#

Yes, I think we need something Astro built-in. I think you should open an issue @jaunty plover

sharp violet
#

I get the same behavior with Splidejs. It should be loaded only on my home page but it is loaded on every pages. <ViewTransitions /> is set on the <head> of the shared layout.

<script>
import { Splide } from "@splidejs/splide";
    const setSlideState = () => {
    const splide = new Splide(".splide",     {
        type: "fade",
        rewind: true
    });
    
    splide.mount();
    }
    
    // Run on initial navigation
    setSlideState();

    // Run on view transitions navigation
    document.addEventListener("astro:after-swap", setSlideState);
</script>

I have tried global state, astro:page-load and astro:after-swap I can not figured it out what i'm doing wrong.

robust comet
jaunty plover
#

Interesting. In the case of AudioEye, it actually needs body scripts to re-excute on page change.

verbal jungle
#

Hey @robust comet! I had left this issue and just went back to MPA but now I need it again so I'm investigating.

I have this code snippet

<script type="text/javascript" src="https://checkoutpage.co/js/overlay.js" defer></script>

That creates an iframe. But when going to a new page, the iframe is not recreated. Is this script re-executed by default (I'm on astro latest)?

robust comet
#

Hi @verbal jungle! No. After the latest changes, scripts are not automatically re-executed on transitions, see note above.

verbal jungle
#

So if I understand well, the only way is to actually modify the 3rd party script?

robust comet
#

In your case it might be enough to analyze the script. To me it seems that the script stores the init functions in window.checkoutPage. If that's the case you could invoke them from an astro:after-swap event listener.

verbal jungle
#

Makes sense thank you! But thinking about Astro users in general, I wonder if there could be a better way to handle this at the framework level. Like, what if the script is damn huge?

verbal jungle
#

I had the same issue with algomo and I ended up creating the script programmatically:

<script is:inline>
  const addAlgomoScript = () => {
    const algomoScript = document.createElement('script');
    algomoScript.src = 'https://app.algomo.com/algomo.min.js';
    algomoScript.setAttribute('widget', '<ID>');
    algomoScript.async = true;
    algomoScript.defer = true;

    document.head.appendChild(algomoScript);
  };

  addAlgomoScript();
  document.addEventListener('astro:after-swap', addAlgomoScript);
</script>
#

Now @robust comet I have another question: this script creates an iframe. Is there a way to persist it?

#

it would dope if we could just add a data-attribute manually

robust comet
#

Now it get's interesting ๐Ÿ™‚ Not straight forward. I'll call back ๐Ÿ™‚

verbal jungle
#

ahah I can't wait to see what you'll come up with

fathom sparrow
#

I had the same with Snipcart,
For me I wanted to load the script on user interaction, to avoid pagespeed drop. For this I used a vue component:

<template>
  <div 
    hidden
    :data-config-load-css="false"
    id="snipcart"
    :data-api-key="props.snipcartKey"
    data-config-modal-style="side"
  ></div>
   
</template>

<script setup>
import { onMounted, ref, watchEffect } from "vue";
import { usePointer } from "@vueuse/core"; 
const scripLoaded = ref(false);
const pointer = usePointer();
const styleSrc = ref("");
const count = ref(0);

const props = defineProps({
  snipcartKey: String,
});

const stop = watchEffect(() => {
  count.value++;

  if (
    (count.value > 1 && pointer.y.value) ||
    pointer.x.value ||
    pointer.isInside.value ||
    pointer.pointerType.value ||
    pointer.pressure.value ||
    pointer.tiltX.value ||
    pointer.twist.value ||
    pointer.width.value ||
    pointer.pointerId.value ||
    pointer.tiltY.value
  ) {
    LoadJS(); 
    scripLoaded.value = true;
    stop();
  }
});

function LoadJS() {
  let script = document.createElement("script");
  script.type = "text/javascript";
  script.src = "https://cdn.snipcart.com/themes/v3.7.1/default/snipcart.js";
  if (!scripLoaded.value) {
    document.head.appendChild(script);
  }
}

 

onMounted(() => {
  if (!window.Snipcart) {
    setTimeout(() => {
      LoadJS();
      scripLoaded.value = true;
    }, 2500);
  }
});
</script>

Then load the component in your base layout with:

          transition:persist 
          client:idle

Now the script is added to the head once

verbal jungle
#

I don't know how snipcart works, does it create an iframe?

fathom sparrow
#

it creates an div

#

but you need the script once right, and then execute it on every page?

verbal jungle
#

Scripts are not reexecuted on page load I think

fathom sparrow
#

check

#

create the iframe in an vue, solid or react component and make use of ' transition:persist '

verbal jungle
#

mmh if the created element is part of the component, I don't know what happens

#

but generally speaking, those kind of scripts usually append it to the whole body

fathom sparrow
#

let me take a look at the docs. 1 moment

#

i must say I cant find api docs of checkoutpage

verbal jungle
#

they don't have great docs regarding this topic

#

Honestly, I won't worry about checkoutpage. I chose another method that opens the link instead

#

The one about algomo (live-chat through multiple iframes) is different and more important imo

#

people wanting to add widgets of intercom, chatwoot etc will face a similar issue

fathom sparrow
#

if you want to use a companent that geeps its state, then use transition:persist this will keep the component active on navigation events

#

#component that keeps

verbal jungle
#

yes I know thank you! But the thing here is that the iframe is wiped on transition because it's created programmatically, I can't add transition:persist on it. But martin said it requires some more work and he will reach back

fathom sparrow
#

most of the 3th party scripts have a target option. so target a div inside your component

verbal jungle
#

mmmh I will ask the team if that's possible, good idea

#

thanks!

fathom sparrow
#

otherwise.. use snipcart ๐Ÿ™‚

#

I am looing at the object and script, in the script it looks like the iframe is added to the document, so it looks like you cant choose a target.
But I see that the checkoutPage object it has some functions.

So you could try to run the init after a page swap

#

good luck!

verbal jungle
#

thanks!

robust comet
#

Hi @verbal jungle, you might try the following:
after you loaded the 3rd party script on page one and it has created the iframe, assign it a name

  document.querySelector("iframe").dataset.astroTransitionPersist="iframe"

(assuming that is the only iframe, otherwise you will sure know a better selector)

On all the other pages where you want to see the iframe after transition, add a placeholder

  <div transition:persist="iframe">When I grow up I will be an iframe</div>
#

Of course, the name you use to identify the I frame as well as the text and even the HTML element type of the placeholder are up to you ๐Ÿ˜‰

#

Rational: You need to set transition:persist on the iframe on the page where you start the navigation. And you need transition:persist on the target page, but this can be on any element as it will be replaced by the original.

#

Since the iframe does not provide transition:persist by itself, you need to set it in your script that triggers the insertion of the iframe

#

The code on the other pages is standard Astro code.

#

I'm curious if this approach works for you. Keep me posted on your progress!

verbal jungle
#

Looks awesome thank you! I'm gonna try that rn

robust comet
#

do you have some main page for the first occurence of the iframe or are there a bunch of equal pages of which each can be the "first page"?

verbal jungle
#

They can all be the first page but that's not a big deal. I'm deleting the div when creating the iframe to make sure it's only done the first time

#

At least, that's what I'm trying

robust comet
verbal jungle
#

Still not working but here is where I am:

I added those divs in my global layout:

  <body class="antialiased flex min-h-full flex-col bg-white dark:bg-gray-950">
    <slot />
    <div transition:persist="algomo-widget-body-iframe" />
    <div transition:persist="algomo-widget-trigger-iframe" />
    <div transition:persist="algomo-widget-popup-iframe" />
  </body>

And then I updated my script in the global head:

<script is:inline>
  document.addEventListener('DOMContentLoaded', () => {
    const algomoScript = document.createElement('script');
    algomoScript.src = 'https://app.algomo.com/algomo.min.js';
    algomoScript.setAttribute('widget', '<WIDGET_ID>');
    algomoScript.async = true;
    algomoScript.defer = true;

    const placeholders = Array.from(document.querySelectorAll('[data-astro-transition-persist^="algomo-widget"]'));

    for (const placeholder of placeholders) {
      placeholder.remove();
    }

    document.head.appendChild(algomoScript);

    let observer;

    observer = new MutationObserver(() => {
      const iframes = Array.from(document.querySelectorAll('[id^="algomo-widget"]'));

      if (iframes.length !== 0) {
        for (const iframe of iframes) {
          iframe.dataset.astroTransitionPersist = iframe.id;
        }
        observer.disconnect();
      }
    });

    observer.observe(document.documentElement, {
      attributeFilter: ['[id^="algomo-widget"]'],
      subtree: true,
      childList: true,
    });
  });
</script>
#

So now a few...unexpected things happen:

  1. The iframes quickly disappear and reappear
  2. The frames are now stuck at the end of the document ๐Ÿค”
#

Okay I know why, that's because the script injects a stylesheet, so that's not done on subsequent navigations

#

So I added this which solves problem #2

  document.addEventListener("astro:after-swap", () => {
    const stylesheet = document.createElement("link")
    stylesheet.rel = "stylesheet"
    stylesheet.type = "text/css",
    stylesheet.href = "https://app.algomo.com/algomo.min.css"

    document.head.appendChild(stylesheet)
  })
#

But I still don't know why the iframes quickly disappear

#

Looks like they're removed and added, any idea why?

robust comet
#

Gone a long way! I was afraid that you had to wait for the iframe to appear. Nice solution ๐Ÿ™‚

#

When is it, that the iframe blinks? on navigation?

#

Or on insertion?

verbal jungle
#

On navigation

robust comet
#

that could be the stylesheet? Navigation removes it, you re-add it. some animation / visibility stuff restarts?

verbal jungle
#

Maybe, let me try adding it statically to the head to see if it changes anything

robust comet
#

It is such a pleasure to pair programm with you ๐Ÿ™‚ I do not even have to suggest anything by myself ๐Ÿ™‚

verbal jungle
#

it's crazy how just exchanging with someone else helps your brain find solutions

#

Okay, I can confirm that's unrelated

robust comet
#

thats not okay ๐Ÿ™‚

verbal jungle
#

I suspect iframes being removed from the dom

#

I'm going to do a shitty setInterval and see what's going on

robust comet
#

Good luck! Hit me up if I can be of further assistance

verbal jungle
#

Thanks! I'll probably reach out but in the meantime, I'll see what I can get

#

I appreciate the help ^^

#

I'll keep documenting what I'm doing here, don't worry about all the messages, I'll ping you if I need anything ๐Ÿ‘

#

So I pasted the following in the console

setInterval(() => console.log(document.querySelectorAll('[id^="algomo-widget"]').length), 10);

And elements are never removed from the DOM, maybe because they are div at first and iframes which is unexpected

#

Also tried that

setInterval(() => console.log(Array.from(document.querySelectorAll('[id^="algomo-widget"]')).map(t => t.tagName)), 10);

but always getting ['IFRAME', 'IFRAME', 'IFRAME']

robust comet
#

you should only load the script on the first time

verbal jungle
#

or the huge script above?

robust comet
#

give me a sec to have a look

verbal jungle
#

Sure! Tell me if you want me to upload a preview branch on vercel

robust comet
verbal jungle
#

Yep so that's good, I thought I might get 3 divs at some point

verbal jungle
robust comet
#

The divs never reach the browser. The are replaced in the document with the iframes before it is inserted into the DOM

robust comet
verbal jungle
robust comet
#

what is the path i should walk in the app?

verbal jungle
#

Actually, I don't know if issue is on Astro side (unlikely) or on some things in the algomo script

robust comet
#

me neither ๐Ÿ™‚

verbal jungle
#

latest version is ready, hard refresh to be sure the stylesheet fix is applied

#

I don't think it's a matter of styling (opacity or display) but a matter of mounting/unmounting:

  1. Go to index page and refresh
  2. click on the chat icon
  3. type something without submitting (eg. xxx)
  4. close the popup by clicking on the top-right x icon
  5. reopen it
    => xxx is still there

Now try this:

  1. Go to index page and refresh
  2. click on the chat icon
  3. type something without submitting (eg. xxx)
  4. close the popup by clicking on the top-right x icon
  5. navigate to a new page
  6. reopen it
    => xxx is not there anymore
robust comet
#

The iframe reloads its content when it is re-inserted on the target page. Therefore, persisting an iframe is not a valid approach to preserve its state across pages. I am sorry that this is not a solution to your issues.