app-staging.skillfit.me Open in urlscan Pro
13.32.151.80  Public Scan

URL: https://app-staging.skillfit.me/
Submission: On December 26 via api from US — Scanned from US

Form analysis 0 forms found in the DOM

Text Content

(function () {
  // baseUrl DYNAMICALLY CHANGED - DO NOT CHANGE THIS UNLESS YOU CHANGE iframe_copy_script.js AS WELL
  const baseUrl = 'https://embed-staging.skillfit.me';

  const SCRIPT_ATTRIBUTE_CLIENT_ID_KEY = 'data-emsi-client-id';
  const TARGET_PATH_QUERY_PARAM_KEY = '_z';
  const TARGET_DIV_ID = 'emsi-skills-div-app-001';
  const IFRAME_ID = 'emsi-skills-iframe-app-001';
  const SLASH_DELIMITER = '--';
  const EMSI_APP_NAME = 'Emsi skill app';

  const SEARCH_QUERY_PARAM_KEY = '_s'; // DO NOT UPDATE WITHOUT also updating iFrameCommunication.ts
  const EQUAL_SIGN_PLACEHOLDER = '1*0_0*1'; // DO NOT UPDATE WITHOUT also updating iFrameCommunication.ts
  const AMPERSAND_PLACEHOLDER = '-4*7_'; // DO NOT UPDATE WITHOUT also updating iFrameCommunication.ts
  const QUESTION_MARK_PLACEHOLDER = '0.._-..0'; // DO NOT UPDATE WITHOUT also updating iFrameCommunication.ts

  const EVENT_NAME_URL_CHANGE = 'urlChange';
  const EVENT_NAME_SET_WINDOW_SCROLL_POSITION = 'setWindowScrollPosition';
  const EVENT_NAME_GO_TO_WINDOW_SCROLL_POSITION = 'goToWindowScrollPosition';

  function checkIfInJest() {
    return typeof jest !== 'undefined';
  }

  const getSanitizedURLSearch = (search) => {
    // we want to pass `search` from the caller to decide what to do between `decodeURIComponent` and `encodeURIComponent`
    // example: converting `--` to `%2F` or vise versa
    while (search.startsWith('?') || search.startsWith('%3F')) {
      if (search.startsWith('?')) {
        search = search.replace('?', '');
      } else {
        search = search.replace('%3F', '');
      }
    }

    const searchObject = new URLSearchParams(search);

    return searchObject;
  };

  function handleBrowserNavigation() {
    console.log(`handleBrowserNavigation:popstate()`, window);

    window.addEventListener('popstate', (event) => {
      if (!window.haltPopState) {
        console.log(
          'iframe.js:window.POPSTATE::::: location: ' + document.location + ', state: ' + JSON.stringify(event.state)
        );

        console.log(`window.iframeResizer`, window.iframeResizer);

        const formattedSearch = convertSlashReplacements(window.location.search);
        const params = formattedSearch ? getSanitizedURLSearch(formattedSearch) : new URLSearchParams();

        const path = params.get(TARGET_PATH_QUERY_PARAM_KEY) || '';
        params.delete(TARGET_PATH_QUERY_PARAM_KEY);

        const newRouteForIFrameChild = `${path}?${params.toString()}`;

        window.iframeResizer[0].iFrameResizer.sendMessage(
          {
            eventName: 'browser_navigation_back',
            newRoute: newRouteForIFrameChild,
          },
          ['*']
        );

        // SUPERHACK: Firefox, for whatever reason, wants to fire off an additional POPSTATE once we call `window.history.go(-2)` down below
        // This is to prevent a rapid chain of navigations.
        window.haltPopState = true;
        setTimeout(() => (window.haltPopState = false), 250);
      }
    });
  }

  /**
   * Converts our slash delimiter to html encoded slashes ('%2F' === `/`)
   * '_z=account--sign-in' -> _z=account%2Fsign-in
   */
  function convertSlashReplacements(param) {
    const replaceAll = (str, find, rep) => str.split(find).join(rep);
    return replaceAll(param, SLASH_DELIMITER, '%2F');
  }

  function addScript(src, callback) {
    var s = document.createElement('script');
    s.setAttribute('src', src);
    s.onload = callback;
    s.onerror = function () {
      console.error(`failed to load iframe resizer script`);
    };
    document.getElementsByTagName('head')[0].appendChild(s);
  }

  function getClientId() {
    var thisScript = document.querySelector(`script[${SCRIPT_ATTRIBUTE_CLIENT_ID_KEY}]`);
    if (!thisScript) {
      console.error(
        `${SCRIPT_ATTRIBUTE_CLIENT_ID_KEY} key must exist. Add the ${SCRIPT_ATTRIBUTE_CLIENT_ID_KEY}=<emsi id> data attribute to your script tag`
      );
      return false;
    }
    const clientId = thisScript.getAttribute(SCRIPT_ATTRIBUTE_CLIENT_ID_KEY);
    if (!clientId) {
      console.error(`${SCRIPT_ATTRIBUTE_CLIENT_ID_KEY} key was empty. Populate your client id`);
      return;
    }
    return clientId;
  }
  /**
   * Create iframe.src variable. Allow empty _z key.
   */
  function constructIframeSource() {
    const formattedSearch = convertSlashReplacements(window.location.search);
    const urlParams = getSanitizedURLSearch(formattedSearch);
    const path = urlParams.get(TARGET_PATH_QUERY_PARAM_KEY) || ''; // empty string so we don't add `null` to url
    const hashedSearchParams = urlParams.get(SEARCH_QUERY_PARAM_KEY);
    if (hashedSearchParams) {
      const appSearchParams = new URLSearchParams(
        hashedSearchParams
          .replaceAll(EQUAL_SIGN_PLACEHOLDER, '=')
          .replaceAll(AMPERSAND_PLACEHOLDER, '&')
          .replaceAll(QUESTION_MARK_PLACEHOLDER, '?')
      );
      for (const key of appSearchParams.keys()) {
        urlParams.delete(key);
        appSearchParams.getAll(key).forEach((value) => urlParams.append(key, value));
      }
    }

    urlParams.append('parentLocation', window.location.origin + window.location.pathname);
    urlParams.append('clientId', getClientId());
    urlParams.delete(TARGET_PATH_QUERY_PARAM_KEY);
    urlParams.delete(SEARCH_QUERY_PARAM_KEY);
    const queryParam = `?${urlParams.toString()}`;

    return `${baseUrl}/${path}${queryParam}`;
  }

  function mutateStyle(element) {
    element.style.border = '0';
    element.style.minWidth = '100%';
    element.style.width = '1px';
  }

  function updateNavState(state = {}, unusedString = '', url) {
    // with replaceState, popState doesn't work, so we opted with pushState, and now we get double history
    // this is required because without it, the child iframe nav doesn't update the parent page nav, and so using
    // browser nav would be broken
    window.history.pushState(state, unusedString, url);
  }

  function onUrlChangeMessage(message) {
    console.info(`Attempting to update parent query params with: ${JSON.stringify(message)}`);
    const { newAppPath, newAppSearchQueryString, title, isNavigatingBack } = message;
    const currUrlParams = getSanitizedURLSearch(window.location.search);

    const iframe = document.getElementById(IFRAME_ID);
    // If statement is an Edge case in which the iFrame is still being intialized
    if (iframe) {
      iframe.title = `SkillFit - ${title}`;
    }
    currUrlParams.set(TARGET_PATH_QUERY_PARAM_KEY, newAppPath);
    if (newAppSearchQueryString) {
      currUrlParams.set(SEARCH_QUERY_PARAM_KEY, newAppSearchQueryString);
    } else {
      currUrlParams.delete(SEARCH_QUERY_PARAM_KEY);
    }

    const fullPath = `${window.location.origin}${window.location.pathname}${
      currUrlParams.toString().length ? '?' + currUrlParams.toString().replace(/%2F/g, SLASH_DELIMITER) : ''
    }`;

    if (isNavigatingBack) {
      console.info('Navigating back');
      // to avoid the problem of double history being recorded
      window.history.go(-2);
    } else {
      console.info(`Parent url update: `, fullPath);
      updateNavState({ title }, '', fullPath);
    }
  }

  function setWindowScrollPosition() {
    const scrollPosition = { scrollX: window.scrollX, scrollY: window.scrollY };
    window.__iframe_temp_store = { scrollPosition };
  }

  function scrollToWindowScrollPosition() {
    const scrollPosition = (window.__iframe_temp_store || {}).scrollPosition;
    if (!scrollPosition) {
      return;
    }
    window.scrollTo(scrollPosition.scrollX, scrollPosition.scrollY);
    window.__iframe_temp_store = { scrollPosition: undefined };
  }

  function createIframe() {
    const src = constructIframeSource();

    console.log(`src`, src);

    if (!src) {
      console.error(`${EMSI_APP_NAME} could not find url param. Make sure you create a url with a valid route`);
      return false;
    }

    var iframe = document.createElement('iframe');
    iframe.src = src;
    iframe.id = IFRAME_ID;
    iframe.title = 'SkillFit';
    mutateStyle(iframe);

    const parentDiv = document.getElementById(TARGET_DIV_ID);
    mutateStyle(parentDiv);

    if (!parentDiv) {
      console.error(
        `${EMSI_APP_NAME} could not find target div element. Make sure you create a div with the id ${TARGET_DIV_ID}`
      );
      return false;
    }
    parentDiv.appendChild(iframe);

    return true;
  }

  function hasES6support() {
    try {
      Function('() => {};');
      return true;
    } catch (exception) {
      return false;
    }
  }

  function setup() {
    if (checkIfInJest()) return;
    if (!hasES6support()) {
      console.error(`Your browser does not support required features. Try using chrome`);
    }

    const success = createIframe();
    if (!success) {
      console.error(`${EMSI_APP_NAME} failed to load`);
      return;
    }

    addScript('https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.1/iframeResizer.min.js', function () {
      // iFrameResize() is included in the above-script...
      // eslint-disable-next-line no-undef
      window.iframeResizer = iFrameResize(
        {
          heightCalculationMethod: 'lowestElement',
          log: false,
          targetOrigin: '*',
          onMessage: function (param) {
            // this is triggered by the inframed page navigating
            const eventName = (param.message || {}).eventName;
            if (eventName === EVENT_NAME_URL_CHANGE) {
              if (param.message) {
                onUrlChangeMessage(param.message);
              }
            } else if (eventName === EVENT_NAME_SET_WINDOW_SCROLL_POSITION) {
              setWindowScrollPosition();
            } else if (eventName === EVENT_NAME_GO_TO_WINDOW_SCROLL_POSITION) {
              scrollToWindowScrollPosition();
            } else {
              console.warn(`Bad event name ${eventName}`);
            }
          },
        },
        `#${IFRAME_ID}`
      );
    });

    handleBrowserNavigation();

    console.info(`${EMSI_APP_NAME} iframe initialized`);
  }

  setup();

  // This is a hack for unit testing import, should probably rewrite to use webpack for bundling this file if we want to use more functions from here
  if (typeof exports === 'object') {
    module.exports = { onUrlChangeMessage };
  }
})();