Jira helpdesk widget doesn't create an iframe when script is loaded dynamically

15 views Asked by At

I'm trying to load the Jira helpdesk widget after user makes an action by loading the script Jira provides dynamically in code like this

  let script = document.createElement('script');    
  
  script.setAttribute('data-jsd-embedded', '');
  script.setAttribute('data-key', 'key value');
  script.setAttribute('data-base-url', 'https://jsd-widget.atlassian.com');
  script.setAttribute('src', 'https://jsd-widget.atlassian.com/assets/embed.js');  
  
  document.head.appendChild(script);

However while it shows in Sources tab in Chrome that it loaded the script, the associated iframe that contains the widget doesn't get created as it should happen normally, and I don't see any error messages in the console.

When I just use a script tag in the HTML file like this

<head>    
    ...
    <script data-jsd-embedded data-key="key value" data-base-url="https://jsd-widget.atlassian.com" src="https://jsd-widget.atlassian.com/assets/embed.js"></script>
</head>

Everything works perfectly and there are more additional resources/scripts fetched besides embed.js.

Why doesn't it work the same with creating a script tag programmatically in JS code? How do I make it work? As far as I can tell the end result is the same - you get the same script tag.

EDIT I seem to have located the non-minified version of the embed.js and will include below

import MutationObserverHandler from "embeddable/widget/mutation-observer-handler";
import ReactDevTools from "embeddable/widget/util/react-dev-tools";
import * as Sentry from "@sentry/browser";

const jsdWidgetID = "jsd-widget";
const jsdWidgetName = "JSD widget";

class Iframe {
    el: HTMLElement;
    document: Document;

    constructor(iframeScriptSrc: string, embeddableData: {}) {
        this.createAndInsertIframe(document.body);
        this.applyCss();
        this.writeDocument(iframeScriptSrc, embeddableData);
        new MutationObserverHandler(this.document, this.css.bind(this));
    }

    createAndInsertIframe(parent: HTMLElement) {
        const iframe = document.createElement("iframe");
        iframe.scrolling = "no";
        iframe.name = jsdWidgetName;
        iframe.id = jsdWidgetID;
        parent.appendChild(iframe);
        this.el = iframe;
        const contentWindow = iframe.contentWindow;
        // check for null, which according to typescript dom lib >=2.9 can happen  (wasn't the case with 2.6)
        if (!contentWindow) {
            Sentry.captureMessage("iframe.contentWindow is null", Sentry.Severity.Error);
            throw new Error("Can't setup jsd-widget iframe");
        }
        this.document = contentWindow.document;
    }

    applyCss() {
        this.css("height", "1px");
        this.css("width", "1px");
        this.css("position", "fixed");
        this.css("border", "0");
        this.css("bottom", "0");
        this.css("right", "0");
        this.css("z-index", "9999999999999");
    }

    writeDocument(iframeScriptSrc: string, embeddableData: EmbeddableData) {
        const scriptTag = `<script type="application/javascript" src="${iframeScriptSrc}"></script>`;
        const embeddableDataJson = JSON.stringify(embeddableData);
        const baseURL = JSON.parse(embeddableDataJson).baseUrl; // added baseURl because lazy loading was not working
        this.document.open();
        this.document.write(
            `<html>
                <head><base href='${baseURL}' /></head>
                    <body data-embeddable=${embeddableDataJson}>
                        <div id="react-root"></div>
                        ${embeddableData.reactDevtools !== undefined ? ReactDevTools.scriptTag : ""}
                        ${scriptTag}
                    </body>
             </html>`
        );
        this.document.close();
    }

    css(property: string, value: string) {
        this.el.style[property] = value;
    }
}

document.addEventListener("DOMContentLoaded", () => {
    const script = document.currentScript || document.querySelector("script[data-jsd-embedded]");
    const dataAttributes = script && script instanceof HTMLScriptElement && script.dataset;
    const isJsmWidgetRendered = document.querySelector(`iframe#${jsdWidgetID}`);

    if (isJsmWidgetRendered) {
        // tslint:disable-next-line no-console
        console.error("JSM Widget: could not render more than one widget on a single page");
        return;
    }

    if (!script) {
        // tslint:disable-next-line no-console
        console.error("JSM Widget: could not find jsd-embedded script");
        return;
    }

    if (!dataAttributes) {
        // tslint:disable-next-line no-console
        console.warn(
            "JSM Widget: legacy unsupported browser, jsd-embedded script.dataset was undefined"
        );
        return;
    }

    const scriptSrc = script.getAttribute("src");
    if (!scriptSrc) {
        return;
    }

    const iframeScriptSrc = `${scriptSrc.substring(0, scriptSrc.lastIndexOf("/"))}/iframe.js`;
    new Iframe(iframeScriptSrc, dataAttributes);
});

interface EmbeddableData {
    reactDevtools?: string;
}
0

There are 0 answers