digitalfabrik/integreat-app

View on GitHub
web/src/utils/iframes.ts

Summary

Maintainability
A
25 mins
Test Coverage
A
93%
import { TFunction } from 'i18next'

import { CONSENT_ROUTE, ExternalSourcePermissions } from 'shared'

import { IFRAME_BLANK_SOURCE, IframeSources } from '../components/RemoteContent'
import dimensions from '../constants/dimensions'

export const LOCAL_STORAGE_ITEM_EXTERNAL_SOURCES = 'Opt-In-External-Sources'
export const addDoNotTrackParameter = (iframe: HTMLIFrameElement): void => {
  if (iframe.src.includes('vimeo')) {
    const url = new URL(iframe.src)
    url.searchParams.append('dnt', '1')
    iframe.setAttribute('src', url.href)
  }
}

export const preserveIFrameSourcesFromContent = (
  index: number,
  source: string,
  setExistingIframes: (sources: IframeSources) => void,
  contentIframeSources: IframeSources,
): void => {
  const updatedContentSources: IframeSources = contentIframeSources
  updatedContentSources[index] = source
  setExistingIframes(updatedContentSources)
}

export const hideIframe = (iframe: HTMLIFrameElement): void => {
  iframe.setAttribute('src', IFRAME_BLANK_SOURCE)
  iframe.setAttribute('style', 'display:none')
}

export const restoreIframe = (iframe: HTMLIFrameElement, source: string): void => {
  iframe.setAttribute('src', source)
  iframe.setAttribute('style', 'display:block')
}

const getContainer = (element: HTMLElement, className: string, id: string): HTMLDivElement | null => {
  if (document.getElementById(id)) {
    return null
  }
  const container = document.createElement('div')
  container.id = id
  container.classList.add(className)
  element.appendChild(container)
  return container
}

const getIframeContainer = (
  id: string,
  viewportSmall: boolean,
  iframe: HTMLIFrameElement,
  deviceWidth: number,
): HTMLDivElement => {
  const existingContainer = document.getElementById(id)
  if (existingContainer) {
    return existingContainer as HTMLDivElement
  }
  const iframeContainer = document.createElement('div')
  iframeContainer.classList.add('iframe-container')
  iframeContainer.id = id
  iframe.parentNode?.appendChild(iframeContainer)
  iframeContainer.appendChild(iframe)
  if (viewportSmall) {
    // Scale the height depending on device width minus padding
    const scaledHeight =
      (deviceWidth / Number(iframe.width)) * Number(iframe.height) - dimensions.mainContainerHorizontalPadding
    iframe.setAttribute('height', `${scaledHeight}`)
  } else {
    // Set the container width according to the iframe width
    iframeContainer.setAttribute('style', `width:${iframe.width}px!important`)
  }
  return iframeContainer
}

const removeOptInContainer = (elementId: string) => {
  const element = document.getElementById(elementId)
  if (element) {
    element.remove()
  }
}

const showSource = (element: HTMLElement, source: string): void => {
  const span = document.createElement('span')
  span.classList.add('iframe-source')
  element.appendChild(span)
  span.appendChild(document.createTextNode(source))
  element.appendChild(document.createElement('br'))
}

export const showMessage = (text: string, element: HTMLDivElement, iframeSource: string): void => {
  const textNode = document.createTextNode(text)
  showSource(element, iframeSource)
  element.appendChild(textNode)
}

const showOptIn = (
  text: string,
  iframeContainer: HTMLDivElement,
  source: string,
  updateLocalStorage: (source: string) => void,
  index: number,
): void => {
  const onClickHandler = () => {
    updateLocalStorage(source)
  }

  const className = `iframe-info-text`
  const elementId = `${className}${source}${index}`
  const container = getContainer(iframeContainer, className, elementId)
  if (!container) {
    return
  }

  const id = `opt-in-checkbox-${source}${index}`
  const checkbox = document.createElement('input')
  checkbox.type = 'checkbox'
  checkbox.name = 'opt-in-checkbox'
  checkbox.id = id
  checkbox.onclick = onClickHandler
  const label = document.createElement('label')
  showSource(label, source)
  label.htmlFor = id
  label.appendChild(document.createTextNode(text))
  container.appendChild(label)
  container.appendChild(checkbox)
}

const showSettingsLink = (element: HTMLDivElement, t: TFunction): void => {
  const link = document.createElement('a')
  link.innerHTML = t('layout:settings')
  link.id = 'opt-in-settings-link'
  link.href = `/${CONSENT_ROUTE}`
  element.appendChild(link)
}

const showMessageWithSettings = (
  text: string,
  iframeContainer: HTMLDivElement,
  t: TFunction,
  source: string,
  iframeIndex: number,
  removeOptIn: boolean,
) => {
  if (removeOptIn) {
    removeOptInContainer(`iframe-info-text${source}${iframeIndex}`)
  }
  const className = `iframe-info-text`
  const elementId = `${className}${source}${iframeIndex}`
  const container = getContainer(iframeContainer, className, elementId)
  if (!container) {
    return
  }
  iframeContainer.appendChild(container)
  showMessage(text, container, source)
  showSettingsLink(container, t)
}
export const handleAllowedIframeSources = (
  iframe: HTMLIFrameElement,
  externalSourcePermissions: ExternalSourcePermissions,
  storedIframeSource: string,
  t: TFunction,
  onUpdateLocalStorage: (source: string) => void,
  iframeIndex: number,
  supportedSource: string,
  viewportSmall: boolean,
  deviceWidth: number,
): void => {
  const permission = supportedSource ? externalSourcePermissions[supportedSource] : undefined
  const iframeContainerId = `iframe-container${supportedSource}${iframeIndex}`
  const iframeContainer = getIframeContainer(iframeContainerId, viewportSmall, iframe, deviceWidth)

  if (permission === undefined) {
    const message = t('consent:knownResourceOptIn')
    showOptIn(message, iframeContainer, supportedSource, onUpdateLocalStorage, iframeIndex)
  } else if (permission) {
    restoreIframe(iframe, storedIframeSource)
    // Add do not track parameter (only working for vimeo)
    if (supportedSource === 'vimeo.com') {
      addDoNotTrackParameter(iframe)
    }
    const message = t('consent:knownResourceContentMessage')
    showMessageWithSettings(message, iframeContainer, t, supportedSource, iframeIndex, true)
  } else {
    const message = t('consent:knownResourceBlocked')
    showMessageWithSettings(message, iframeContainer, t, supportedSource, iframeIndex, false)
  }
}