[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/Link.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, {
  MouseEvent,
  useCallback,
  useMemo,
  AnchorHTMLAttributes,
} from 'react'
import { NavigateOptions, pathFromPageName } from '../utils/pages'
import { useRuntime } from './RenderContext'
import { useIsPrefetchActive } from '../hooks/prefetch'
import PrefetchLink from './Prefetch/PrefetchLink'

const isLeftClickEvent = (event: MouseEvent<HTMLAnchorElement>) =>
  event.button === 0

const isModifiedEvent = (event: MouseEvent<HTMLAnchorElement>) =>
  !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey)

const absoluteRegex = /^https?:\/\/|^\/\//i
const telephoneRegex = /^tel:/i
const mailToRegex = /^mailto:/i

const isAbsoluteUrl = (url: string) => absoluteRegex.test(url)
const isTelephoneUrl = (url: string) => telephoneRegex.test(url)
const isMailToUrl = (url: string) => mailToRegex.test(url)

interface Props
  extends NavigateOptions,
    Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
  onClick?: (event: React.MouseEvent) => void
  className?: string
  target?: string
  waitToPrefetch?: number
}

const appendWorkspaceToURL = (
  url: string | undefined,
  workspace: string | undefined
) => {
  if (
    url == null ||
    !workspace ||
    workspace === '' ||
    String(url).indexOf('workspace=') > -1
  ) {
    return url
  }

  const separator = String(url).includes('?') ? '&' : '?'
  return url + separator + 'workspace=' + workspace
}

const Link: React.FunctionComponent<Props> = ({
  page,
  onClick = () => {},
  params,
  to,
  scrollOptions,
  query,
  children,
  modifiers,
  replace,
  modifiersOptions,
  target,
  waitToPrefetch,
  ...linkProps
}) => {
  const {
    pages,
    navigate,
    rootPath = '',
    route: { domain },
    query: queryFromRuntime,
    getSettings,
  } = useRuntime()

  // If workspace is set via querystring, keep it
  const workspace = queryFromRuntime?.workspace

  const storeSettings = getSettings('vtex.store')

  const isPrefetchActive = useIsPrefetchActive()

  const options = useMemo(
    () => ({
      fallbackToWindowLocation: false,
      page,
      params,
      query: workspace ? appendWorkspaceToURL(query ?? '', workspace) : query,
      rootPath,
      scrollOptions,
      to: appendWorkspaceToURL(to, workspace),
      modifiers,
      replace,
      modifiersOptions,
    }),
    [
      page,
      params,
      query,
      workspace,
      rootPath,
      scrollOptions,
      to,
      modifiers,
      replace,
      modifiersOptions,
    ]
  )

  const handleClick = useCallback(
    (event: MouseEvent<HTMLAnchorElement>) => {
      if (
        isModifiedEvent(event) ||
        !isLeftClickEvent(event) ||
        (to && (isAbsoluteUrl(to) || isTelephoneUrl(to) || isMailToUrl(to))) ||
        storeSettings?.useDefaultBrowserNavigation
      ) {
        return
      }

      onClick(event)

      // If you pass a target different from "_self" the component
      // will behave just like a normal anchor element
      if ((target === '_self' || !target) && navigate(options)) {
        event.preventDefault()
      }
    },
    [
      to,
      storeSettings?.useDefaultBrowserNavigation,
      onClick,
      target,
      navigate,
      options,
    ]
  )

  const getHref = () => {
    if (to) {
      // Prefix any non-absolute paths (e.g. http:// or https://) and non-special links (e.g. mailto: or tel:)
      // with runtime.rootPath
      if (
        rootPath &&
        !to.startsWith('http') &&
        !to.startsWith(rootPath) &&
        !isTelephoneUrl(to) &&
        !isMailToUrl(to)
      ) {
        return rootPath + to
      }
      return to
    }
    if (page) {
      const path = pathFromPageName(page, pages, params)
      const qs = query ? `?${query}` : ''
      if (path) {
        return appendWorkspaceToURL(rootPath + path + qs, workspace)
      }
    }
    return '#'
  }

  const href = appendWorkspaceToURL(getHref(), workspace) ?? ''

  // Href inside admin iframe should omit the `/app/` path
  const hrefWithoutIframePrefix =
    domain && domain === 'admin' && href.startsWith('/admin/app/')
      ? href.replace('/admin/app/', '/admin/')
      : href

  const linkElementProps = {
    target,
    href: hrefWithoutIframePrefix,
    ...linkProps,
    onClick: handleClick,
  }

  if (isPrefetchActive) {
    return (
      <PrefetchLink
        waitToPrefetch={waitToPrefetch}
        page={page}
        options={options}
        {...linkElementProps}
      >
        {children}
      </PrefetchLink>
    )
  }

  return <a {...linkElementProps}>{children}</a>
}

export default Link

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/ExtensionPoint.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import ExtensionPoint from './ExtensionPoint/index'

// We need this indirection so its types are exported
export default ExtensionPoint

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/MaybeContext.test.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { cleanup, render } from '@vtex/test-tools/react'
import 'jest-dom/extend-expect'
import MaybeContext from './MaybeContext'
import { Extension } from '../typings/runtime'

afterEach(cleanup)

const TEST_STRING = 'This is category '

beforeAll(() => {
  window.__RENDER_8_COMPONENTS__ = {
    product: function MockProduct(props: any) {
      return (
        <div>
          <h1>{props.title}</h1>
          <p>{TEST_STRING + props.categoryId}!</p>
        </div>
      )
    } as any,
  }
})

test(`it should pass props defined in extensions[path]context.props to the underlying component`, () => {
  const PAGE = 'store.product'
  const mockContextProps = { categoryId: 1, title: 'Product!' }
  const mockExtensions: Record<string, Partial<Extension>> = {
    [PAGE]: {
      context: {
        props: mockContextProps,
        component: 'product',
      },
    },
  }

  const mockRuntime: any = { extensions: mockExtensions }

  const { getByText } = render(
    <MaybeContext nestedPage={PAGE} runtime={mockRuntime} />
  )
  expect(getByText(new RegExp(mockContextProps.title, 'i'))).toBeInTheDocument()
  expect(
    getByText(new RegExp(TEST_STRING + mockContextProps.categoryId, 'i'))
  ).toBeInTheDocument()
})

afterAll(() => {
  delete window.__RENDER_8_COMPONENTS__
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/LayoutContainer.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { FunctionComponent } from 'react'
import { useTreePath } from '../utils/treePath'
import ExtensionPoint from './ExtensionPoint'
import { useRuntime } from './RenderContext'
import { LoadingWrapper } from './LoadingContext'
import { LazyImages } from './LazyImages'
import FoldableContainer from './FoldableContainer'
import { isSiteEditorIframe } from '../utils/dom'
import { Route } from '../typings/runtime'

type Element = string | ElementArray
type ElementArray = Element[]

interface LayoutContainerProps {
  aboveTheFold?: number
  elements: Element
}

interface ContainerProps {
  aboveTheFold?: number
  elements: Element
  isRow: boolean
  isMobile?: boolean
  preview?: boolean
  route?: Route
}

const Container: FunctionComponent<ContainerProps> = ({
  aboveTheFold,
  elements,
  isRow,
  isMobile,
  preview,
  children,
  route,
  ...props
}) => {
  const className = `flex flex-grow-1 w-100 ${
    isRow ? 'flex-row' : 'flex-column'
  }`

  if (typeof elements === 'string') {
    if (elements === '__children__') {
      return <>{children}</>
    }

    const elementIdRegex = /#(.*)/
    const elementId = elements.match(elementIdRegex)
    const containerClass = !elements.includes('fold')
      ? `vtex-render__container-id-${elementId ? elementId[1] : elements}`
      : ''
    return (
      <div className={isRow ? containerClass : className}>
        <ExtensionPoint id={elements} {...props} />
      </div>
    )
  }

  let foldIndex = elements.indexOf('__fold__')
  if (foldIndex === -1) {
    foldIndex = elements.indexOf(`__fold__.${isMobile ? 'mobile' : 'desktop'}`)
  }

  const hasFold = foldIndex > -1

  const lazyImagesFoldPosition = elements.indexOf(
    '__fold__.experimentalLazyImages'
  )
  const hasLazyImagesFold = lazyImagesFoldPosition > -1

  // TODO: Seems to be legacy and unused, might be removed in the future
  let elementsToRender = elements.length
  if (preview && aboveTheFold != null) {
    elementsToRender = aboveTheFold
  }

  const wrappedElements: JSX.Element[] = elements
    .slice(0, elementsToRender)
    .map((element: Element, i: number) => {
      let container = (
        <Container
          key={element.toString()}
          elements={element}
          isMobile={isMobile}
          isRow={!isRow}
          {...props}
        >
          {children}
        </Container>
      )

      if (hasLazyImagesFold && i > lazyImagesFoldPosition) {
        container = (
          <LazyImages key={element.toString()}>{container}</LazyImages>
        )
      }

      return container
    })

  if (!hasFold || isSiteEditorIframe || route?.path?.includes('__siteEditor')) {
    return <div className={className}>{wrappedElements}</div>
  }

  return (
    <div className={className}>
      <FoldableContainer foldIndex={foldIndex}>
        {wrappedElements}
      </FoldableContainer>
    </div>
  )
}

const LayoutContainer: React.FunctionComponent<LayoutContainerProps> = (
  props
) => {
  const { extensions, preview, hints, route } = useRuntime()
  const { treePath } = useTreePath()

  const extension = extensions[treePath]

  const elements =
    extension?.blocks?.map?.((insertion) => insertion.extensionPointId) ?? []
  const containerProps = { ...props, elements }

  const container = (
    <Container
      {...containerProps}
      preview={preview}
      isRow={false}
      isMobile={hints.mobile}
      route={route}
    />
  )

  const isRootTreePath = treePath.indexOf('/') === -1

  if (extension?.preview && isRootTreePath) {
    /** TODO: LoadingWrapper is in the end a makeshift Suspense.
     * Should probably be replaced in the future. */
    return <LoadingWrapper>{container}</LoadingWrapper>
  }

  return container
}

export default LayoutContainer

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/canUseDOM.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { canUseDOM } from 'exenv'

export default canUseDOM

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/Session.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { FunctionComponent, Fragment, useEffect, useState } from 'react'
import Loading from './Loading'
import { useRuntime } from './RenderContext'

interface Props {
  renderWhileLoading?: boolean
}

const Session: FunctionComponent<Props> = ({
  renderWhileLoading,
  children,
}) => {
  const [ensured, setEnsured] = useState(false)
  const [error, setError] = useState(null)
  const { ensureSession } = useRuntime()

  useEffect(() => {
    let isCurrent = true
    if (ensured || error) {
      return
    }

    ensureSession()
      .then(() => {
        if (isCurrent) {
          setEnsured(true)
        }
      })
      .catch((err: any) => {
        if (isCurrent) {
          setError(err)
        }
      })

    return () => {
      isCurrent = false
    }
  }, [ensureSession, ensured, error])

  if (ensured || renderWhileLoading) {
    return <Fragment>{children}</Fragment>
  }

  if (error) {
    return (
      <div className="bg-washed-red pa6 f5 serious-black br3 pre">
        <span>Error initializing session</span>
        <pre>
          <code className="f6">{error}</code>
        </pre>
      </div>
    )
  }

  return <Loading />
}

export default Session

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/FoldableContainer.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { FunctionComponent } from 'react'
import LazyRender from './LazyRender'

interface Props {
  foldIndex?: number
}
const FoldableContainer: FunctionComponent<Props> = ({
  children,
  foldIndex,
}) => {
  if (foldIndex == null || foldIndex === -1) {
    return <>{children}</>
  }
  const childrenArray = React.Children.toArray(children)
  const aboveTheFold = childrenArray.slice(0, foldIndex)
  const belowTheFold = childrenArray.slice(foldIndex).map((element, i) => {
    return <LazyRender key={i}>{element}</LazyRender>
  })

  return (
    <>
      {aboveTheFold}
      {belowTheFold}
    </>
  )
}

export default FoldableContainer

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/LoadingBar.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import styles from './LoadingBar.css'

const LoadingBar = () => (
  <div
    className={`${styles.loadingBarAnimation} fixed top-0 left-0 right-0 z-max bg-action-primary`}
    style={{ height: 4 }}
  >
    <div
      style={{
        background:
          'linear-gradient(90deg, rgba(255, 255, 255, 0.1) 80%, rgba(255, 255, 255, 0.5) 90%, rgba(255, 255, 255, 0.8))',
        width: '100%',
        height: '100%',
      }}
    />
  </div>
)

export default LoadingBar

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/RenderProvider.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import debounce from 'debounce'
import { canUseDOM } from 'exenv'
import { equals, merge, mergeWith, difference } from 'ramda'
import { History, UnregisterCallback, LocationListener } from 'history'
import PropTypes from 'prop-types'
import React, { Component, Fragment, ReactElement, Suspense } from 'react'
import { ApolloProvider } from 'react-apollo'
import { Helmet } from 'react-helmet'
import { IntlProvider } from 'react-intl'

import {
  fetchAssets,
  getLoadedImplementation,
  hasComponentImplementation,
  hotReloadOverrides,
  hotReloadTachyons,
  prefetchAssets,
} from '../utils/assets'
import {
  traverseComponent,
  traverseListOfComponents,
  fetchComponents,
  isConflictingLoadedComponents,
} from '../utils/components'
import { setCookie } from '../utils/cookie'
import {
  isSiteEditorIframe,
  RENDER_CONTAINER_CLASS,
  ROUTE_CLASS_PREFIX,
  routeClass,
} from '../utils/dom'
import { isEnabled } from '../utils/flags'
import { appendLocationSearch } from '../utils/location'
import {
  goBack as pageGoBack,
  mapToQueryString,
  navigate as pageNavigate,
  NavigateOptions,
  NavigationRouteChange,
  NavigationRouteModifier,
  queryStringToMap,
  scrollTo as pageScrollTo,
} from '../utils/pages'
import {
  fetchDefaultPages,
  fetchNavigationPage,
  fetchServerPage,
} from '../utils/routes'
import { TreePathContextProvider } from '../utils/treePath'
import BuildStatus from './BuildStatus'
import ExtensionManager from './ExtensionPoint/ExtensionManager'
import ExtensionPoint from './ExtensionPoint'
import { RenderContextProvider } from './RenderContext'
import type { RenderContext } from './RenderContext'
import RenderPage from './RenderPage'
import {
  getPrefetechedData,
  PrefetchContextProvider,
} from './Prefetch/PrefetchContext'
import { withDevice, WithDeviceProps, DeviceInfo } from '../utils/withDevice'
import { ApolloClientFunctions } from '../utils/client'
import {
  ConfigurationDevice,
  ApolloClientType,
  RenderHistoryLocation,
  SetQueryOptions,
  RenderScrollOptions,
  ParsedServerPageResponse,
  ParsedPageQueryResponse,
  PageContextOptions,
} from '../typings/global'
import { RenderRuntime, Components, Extension } from '../typings/runtime'
import { getMessages as getErrorPageMessages } from './ErrorPage/messages'

// TODO: Export components separately on @vtex/blocks-inspector, so this import can be simplified
const InspectorPopover = React.lazy(
  () =>
    new Promise<{ default: any }>((resolve) => {
      import('@vtex/blocks-inspector').then((BlocksInspector) => {
        resolve({ default: BlocksInspector.default.InspectorPopover })
      })
    })
)

interface Props {
  children: ReactElement<any> | null
  history: History | null
  root: string
  runtime: RenderRuntime
  sessionPromise: Promise<void>
  apollo: ApolloClientFunctions
}

export interface RenderProviderState {
  appsEtag: RenderRuntime['appsEtag']
  cacheHints: RenderRuntime['cacheHints']
  components: RenderRuntime['components']
  culture: RenderRuntime['culture']
  defaultExtensions: RenderRuntime['defaultExtensions']
  device: ConfigurationDevice
  deviceInfo: RenderRuntime['deviceInfo']
  extensions: RenderRuntime['extensions']
  inspect: RenderRuntime['inspect']
  messages: RenderRuntime['messages']
  page: RenderRuntime['page']
  pages: RenderRuntime['pages']
  preview: RenderRuntime['preview']
  production: RenderRuntime['production']
  query: RenderRuntime['query']
  settings: RenderRuntime['settings']
  route: RenderRuntime['route']
  loadedDevices: RenderRuntime['loadedDevices']
  loadedPages: Set<string>
  blocksTree?: RenderRuntime['blocksTree']
  blocks?: RenderRuntime['blocks']
  contentMap?: RenderRuntime['contentMap']
}

const SEND_INFO_DEBOUNCE_MS = 100
const DISABLE_PREFETCH_PAGES = '__disablePrefetchPages'

const noop = () => {}

const areOptionsDifferent = (a: NavigateOptions, b: NavigateOptions) => {
  return (
    a.page !== b.page ||
    a.query !== b.query ||
    a.to !== b.to ||
    a.rootPath !== b.rootPath ||
    !equals(a.params, b.params)
  )
}

const prependRootPath = (path: string, rootPath?: string) => {
  if (!rootPath) {
    return path
  }

  const maybeSlash = path.startsWith('/') ? '' : '/'
  return `${rootPath}${maybeSlash}${path}`
}

interface NavigationState {
  isNavigating: boolean
  lastOptions?: NavigateOptions
}

export class RenderProvider extends Component<
  Props & WithDeviceProps,
  RenderProviderState
> {
  navigationState: NavigationState = { isNavigating: false }
  public static childContextTypes = {
    account: PropTypes.string,
    addMessages: PropTypes.func,
    amp: PropTypes.bool,
    binding: PropTypes.object,
    blocks: PropTypes.object,
    blocksTree: PropTypes.object,
    contentMap: PropTypes.object,
    components: PropTypes.object,
    culture: PropTypes.object,
    defaultExtensions: PropTypes.object,
    // TODO: the prop "device" might be legacy. Figure out if it's still being used.
    device: PropTypes.string,
    // named "deviceInfo" to avoid conflicts with the possibly legacy "device".
    deviceInfo: PropTypes.shape({
      type: PropTypes.oneOf(['desktop', 'tablet', 'phone', 'unknown']),
      isMobile: PropTypes.bool,
    }),
    isMobile: PropTypes.bool,
    emitter: PropTypes.object,
    ensureSession: PropTypes.func,
    extensions: PropTypes.object,
    fetchComponent: PropTypes.func,
    fetchComponents: PropTypes.func,
    getSettings: PropTypes.func,
    goBack: PropTypes.func,
    hints: PropTypes.object,
    history: PropTypes.object,
    inspect: PropTypes.bool,
    messages: PropTypes.object,
    navigate: PropTypes.func,
    onPageChanged: PropTypes.func,
    page: PropTypes.string,
    pages: PropTypes.object,
    patchSession: PropTypes.func,
    platform: PropTypes.string,
    prefetchDefaultPages: PropTypes.func,
    addNavigationRouteModifier: PropTypes.func,
    prefetchPage: PropTypes.func,
    preview: PropTypes.bool,
    production: PropTypes.bool,
    publicEndpoint: PropTypes.string,
    query: PropTypes.object,
    renderMajor: PropTypes.number,
    rootPath: PropTypes.string,
    route: PropTypes.object,
    setDevice: PropTypes.func,
    setQuery: PropTypes.func,
    updateComponentAssets: PropTypes.func,
    updateExtension: PropTypes.func,
    updateRuntime: PropTypes.func,
    workspace: PropTypes.string,
    navigationRouteModifiers: PropTypes.object,
  }

  public static propTypes = {
    children: PropTypes.element,
    history: PropTypes.object,
    root: PropTypes.string,
    runtime: PropTypes.object,
  }

  public sendInfoFromIframe = debounce(
    (params?: { shouldUpdateRuntime?: boolean }) => {
      if (!isSiteEditorIframe) {
        return undefined
      }

      return window.top.__provideRuntime(
        this.getChildContext(),
        this.state.messages,
        (params && params.shouldUpdateRuntime) || false,
        // Deprecated
        this.updateMessages
      )
    },
    SEND_INFO_DEBOUNCE_MS
  )

  private rendered!: boolean
  private sessionPromise: Promise<void>
  private unlisten!: UnregisterCallback | undefined
  private apolloClient: ApolloClientType
  private hydrateApollo: ApolloClientFunctions['hydrate']
  private prefetchRoutes: Set<string>
  public navigationRouteModifiers: Set<NavigationRouteModifier>
  private navigationModifierOptions: Record<string, NavigationRouteChange>
  private fetcher: GlobalFetch['fetch']

  public constructor(props: Props & WithDeviceProps) {
    super(props)
    const {
      appsEtag,
      binding,
      blocks,
      blocksTree,
      cacheHints,
      contentMap,
      culture,
      messages,
      components,
      exposeBindingAddress,
      extensions,
      isJanusProxied,
      pages,
      page,
      query,
      production,
      rootPath = '',
      route,
      settings,
      loadedDevices,
    } = props.runtime
    const { apollo, history, deviceInfo, sessionPromise } = props
    const ignoreCanonicalReplacement = isJanusProxied || (query && query.map)
    this.fetcher = fetch

    if (binding && canUseDOM) {
      setCookie('vtex_binding_address', binding.canonicalBaseAddress)
    }

    if (history) {
      const renderLocation: RenderHistoryLocation = {
        ...history.location,
        search:
          exposeBindingAddress && binding
            ? appendLocationSearch(history.location.search, {
                __bindingAddress: binding.canonicalBaseAddress,
              })
            : history.location.search,
        pathname:
          ignoreCanonicalReplacement || !route.canonicalPath
            ? history.location.pathname
            : rootPath + route.canonicalPath,
        state: {
          navigationRoute: {
            id: route.id,
            params: route.params,
            path: history.location.pathname,
          },
          renderRouting: true,
        },
      }
      // TODO: use something with better performance than equals
      if (!equals(history.location as RenderHistoryLocation, renderLocation)) {
        history.replace(renderLocation)
      }
      // backwards compatibility
      window.browserHistory = global.browserHistory = history
    }

    // todo: reload window if client-side created a segment different from server-side
    this.sessionPromise = sessionPromise
    this.apolloClient = apollo.getClient(this)
    this.hydrateApollo = apollo.hydrate

    this.state = {
      appsEtag,
      blocks,
      blocksTree,
      cacheHints,
      contentMap,
      components,
      culture,
      defaultExtensions: {},
      device: 'any',
      loadedDevices: loadedDevices ?? [props.deviceInfo.type],
      deviceInfo,
      extensions,
      loadedPages: new Set([page]),
      messages,
      page,
      pages,
      preview: false,
      production,
      query,
      route,
      settings: settings || {},
      inspect: false,
    }

    this.prefetchRoutes = new Set<string>()
    this.navigationRouteModifiers = new Set()
    this.navigationModifierOptions = {}
  }

  public componentDidMount() {
    this.rendered = true
    const { history, runtime } = this.props
    const { production, emitter, publicEndpoint } = runtime

    this.unlisten = history?.listen(this.onPageChanged as LocationListener)
    emitter.addListener('localesChanged', this.onLocaleSelected)

    if (!production) {
      emitter.addListener('extensionsUpdated', this.updateRuntime)
      emitter.addListener('blocksUpdated', this.updateRuntime)
      emitter.addListener('styleOverrides', hotReloadOverrides)
      emitter.addListener('styleTachyonsUpdate', hotReloadTachyons)
    }

    this.sendInfoFromIframe()
    this.prefetchPages()

    if (
      publicEndpoint === 'myvtex.com' &&
      !production &&
      '__inspect' in (this.state.query || {})
    ) {
      this.setState({ inspect: true })
    }
  }

  public UNSAFE_componentWillReceiveProps(nextProps: Props) {
    const updatedProps = difference(
      Object.entries(nextProps),
      Object.entries(this.props)
    ).map(([propName]) => propName)

    /** Update all extensions if runtime has changed. */
    const hasUpdatedRuntime = updatedProps.includes('runtime')

    if (this.rendered && hasUpdatedRuntime) {
      const {
        runtime: { extensions },
      } = nextProps
      this.setState({ extensions })
    }
  }

  public componentWillUnmount() {
    const { runtime } = this.props
    const { production, emitter } = runtime
    if (this.unlisten) {
      this.unlisten()
    }
    emitter.removeListener('localesChanged', this.onLocaleSelected)

    if (!production) {
      emitter.removeListener('extensionsUpdated', this.updateRuntime)
      emitter.removeListener('blocksUpdated', this.updateRuntime)
      emitter.removeListener('styleOverrides', hotReloadOverrides)
      emitter.removeListener('styleTachyonsUpdate', hotReloadTachyons)
    }
  }

  public getChildContext(): RenderContext {
    const { history, runtime } = this.props
    const {
      components,
      contentMap,
      extensions,
      inspect,
      messages,
      page,
      pages,
      preview,
      culture,
      device,
      deviceInfo,
      route,
      query,
      defaultExtensions,
    } = this.state

    const {
      account,
      amp,
      binding,
      emitter,
      hints,
      platform,
      production,
      publicEndpoint,
      renderMajor,
      rootPath,
      workspace,
    } = runtime

    return {
      account,
      addMessages: this.addMessages,
      amp,
      binding,
      components,
      contentMap,
      culture,
      defaultExtensions,
      device,
      deviceInfo,
      emitter,
      ensureSession: this.ensureSession,
      extensions,
      fetchComponent: this.fetchComponent,
      fetchComponents: this.fetchComponents,
      getSettings: this.getSettings,
      goBack: this.goBack,
      hints,
      history,
      inspect,
      messages,
      navigate: this.navigate,
      onPageChanged: this.onPageChanged,
      page,
      pages,
      patchSession: this.patchSession,
      platform,
      prefetchDefaultPages: this.prefetchDefaultPages,
      addNavigationRouteModifier: this.addNavigationRouteModifier,
      navigationRouteModifiers: this.navigationRouteModifiers,
      prefetchPage: this.prefetchPage,
      preview,
      production,
      publicEndpoint,
      query,
      renderMajor,
      rootPath,
      route,
      setDevice: this.handleSetDevice,
      setQuery: this.setQuery,
      updateComponentAssets: this.updateComponentAssets,
      updateExtension: this.updateExtension,
      updateRuntime: this.updateRuntime,
      workspace,
    }
  }

  public getSettings = (app: string) => {
    const { settings } = this.state
    return settings[app]
  }

  public ensureSession = () => {
    return this.sessionPromise
  }

  public patchSession = (data?: any) => {
    return this.sessionPromise.then(() =>
      canUseDOM ? window.__RENDER_8_SESSION__.patchSession(data) : undefined
    )
  }

  public getCustomMessages = (locale: string) => {
    const { components } = this.state
    const componentsArray = Object.keys(components)

    const customMessages = componentsArray
      .map(getLoadedImplementation)
      .filter(
        (component) =>
          component &&
          (component.getCustomMessages || component.WrappedComponent)
      )
      .map((component) => {
        const getCustomMessages =
          component.getCustomMessages ||
          (component.WrappedComponent &&
            component.WrappedComponent.getCustomMessages) ||
          noop
        return getCustomMessages(locale)
      })
      .reduce((acc, strings) => ({ ...acc, ...strings }), {})

    return customMessages
  }

  public goBack = () => {
    const { history } = this.props
    return pageGoBack(history)
  }

  public setQuery = (
    query: Record<string, any> = {},
    {
      merge = true,
      replace = false,
      scrollOptions = false,
    }: SetQueryOptions = {}
  ): boolean => {
    const {
      history,
      runtime: { rootPath },
    } = this.props
    const {
      pages,
      page,
      route: { params },
    } = this.state
    if (!history) {
      return false
    }
    const {
      location: { search },
    } = history

    const current = queryStringToMap(search)
    const nextQuery = mapToQueryString(merge ? { ...current, ...query } : query)

    return pageNavigate(history, pages, {
      fetchPage: false,
      skipSetPath: true,
      page,
      params,
      query: nextQuery,
      replace,
      scrollOptions,
      rootPath,
      modifiers: this.navigationRouteModifiers,
    })
  }

  public navigate = (options: NavigateOptions) => {
    const {
      history,
      runtime: { rootPath },
    } = this.props
    const { pages } = this.state
    options.rootPath = rootPath
    options.modifiers = this.navigationRouteModifiers

    this.navigationModifierOptions = {
      ...this.navigationModifierOptions,
      ...options.modifiersOptions,
    }
    options.modifiersOptions = this.navigationModifierOptions

    if (this.navigationState.isNavigating) {
      const lastOptions = this.navigationState.lastOptions!
      if (!areOptionsDifferent(lastOptions, options)) {
        return false
      }
    }

    this.navigationState = {
      isNavigating: true,
      lastOptions: options,
    }

    return pageNavigate(history, pages, options)
  }

  private updateDeviceBlocks = async (deviceInfo: DeviceInfo) => {
    const {
      runtime: { isJanusProxied, rootPath },
    } = this.props

    const {
      route: { path },
    } = this.state

    const query = queryStringToMap(location.search) as RenderRuntime['query']

    const { components, extensions, messages } = await fetchServerPage({
      fetcher: this.fetcher,
      path: prependRootPath(path, rootPath),
      query,
      deviceInfo,
      isJanusProxied,
    })

    await this.fetchComponents(components, extensions)

    this.setState((state) => ({
      extensions: { ...state.extensions, ...extensions },
      components: { ...state.components, ...components },
      messages: { ...state.messages, ...messages },
    }))
  }

  public addNavigationRouteModifier = (modifier: NavigationRouteModifier) => {
    this.navigationRouteModifiers.add(modifier)
  }

  public replaceRouteClass = (route: string) => {
    try {
      const containers = document.getElementsByClassName(RENDER_CONTAINER_CLASS)
      const currentRouteClass = containers[0].className
        .split(' ')
        .find((c) => c.startsWith(ROUTE_CLASS_PREFIX))
      const newRouteClass = routeClass(route)

      Array.prototype.forEach.call(containers, (e: Element) => {
        if (currentRouteClass) {
          e.classList.remove(currentRouteClass)
        }
        e.classList.add(newRouteClass)
      })
    } catch (e) {
      console.error('Failed to set route class', routeClass(route))
    }
  }

  public scrollTo = (scrollOptions?: RenderScrollOptions) => {
    try {
      if (scrollOptions === false) {
        return
      }

      const options = scrollOptions || { top: 0, left: 0 }
      window.setTimeout(() => pageScrollTo(options), 0)
    } catch (e) {
      console.warn('Failed to scroll after page navigation.')
    }
  }

  private mergeRouteParams<T extends { params: any }>(
    matchingPage: T,
    transientRoute: { params: any }
  ): T {
    return {
      ...matchingPage,
      params: {
        ...transientRoute.params,
        ...matchingPage.params,
      },
    }
  }

  public afterPageChanged = (
    route: string,
    scrollOptions?: RenderScrollOptions
  ) => {
    this.navigationState = { isNavigating: false }
    this.replaceRouteClass(route)
    this.scrollTo(scrollOptions)
    this.sendInfoFromIframe()
  }

  public onPageChanged = (location: RenderHistoryLocation) => {
    const {
      runtime: { renderMajor, query: queryFromRuntime, isJanusProxied },
      history,
    } = this.props

    const {
      culture: { locale },
      pages: pagesState,
      production,
      route,
      loadedPages,
      deviceInfo,
    } = this.state
    const { state } = location

    // In case of other router's navigation, or when preventRemount is true, do nothing
    if (!state || !state.renderRouting || state.preventRemount) {
      return
    }

    const { navigationRoute, fetchPage } = state
    const { id: maybePage, params } = navigationRoute
    const transientRoute = { ...route, ...navigationRoute }

    // We always have to navigate to a page. If none was found, we
    // navigate to the current page with preview
    const allowConditions =
      pagesState[maybePage] && pagesState[maybePage].allowConditions
    const declarer = pagesState[maybePage] && pagesState[maybePage].declarer
    const shouldSkipFetchNavigationData =
      (!allowConditions && loadedPages.has(maybePage)) || fetchPage === false
    const query = queryStringToMap(location.search) as RenderRuntime['query']
    const page = maybePage || route.id

    if (shouldSkipFetchNavigationData) {
      return this.setState(
        {
          page,
          query,
          route: transientRoute,
        },
        () => this.afterPageChanged(page, state.scrollOptions)
      )
    }

    const paramsJSON = JSON.stringify(params)
    const apolloClient = this.apolloClient
    const routeId = page

    const {
      prefetchedPathData,
      routeData,
      destinationRouteId,
    } = getPrefetechedData(navigationRoute.path)

    if (prefetchedPathData && routeData && destinationRouteId) {
      const routeId = destinationRouteId
      const matchingPage = prefetchedPathData.matchingPage
      const contentResponse = prefetchedPathData.contentResponse
      let extensions = routeData.extensions
      let messages = routeData.messages
      if (contentResponse) {
        //create a fresh copy of extensions to not change the one in memory
        extensions = JSON.parse(JSON.stringify(routeData.extensions))
        messages = { ...messages, ...contentResponse.contentMessages }
        for (const {
          treePath,
          contentJSON,
          contentIds,
        } of contentResponse.extensionsContent || []) {
          if (contentJSON !== '{}') {
            extensions[treePath]!.content = contentJSON
              ? JSON.parse(contentJSON)
              : undefined
          }
          extensions[treePath]!.contentIds = contentIds
        }
      }

      this.hydrateApollo(prefetchedPathData.queryData).then(() => {
        this.setState(
          (state) => ({
            ...state,
            components: { ...state.components, ...routeData.components },
            extensions: { ...state.extensions, ...extensions },
            loadedPages: loadedPages.add(routeId),
            messages: { ...state.messages, ...messages },
            page: routeId,
            preview: false,
            query,
            route: this.mergeRouteParams(matchingPage, transientRoute),
          }),
          () => {
            this.navigationState = { isNavigating: false }
            this.replaceRouteClass(routeId)
            this.sendInfoFromIframe()
            this.scrollTo(state.scrollOptions)
          }
        )
      })

      return Promise.resolve()
    }

    // Sets the preloading state, which currently displays
    // a loading bar at the top of the page
    this.setState({
      preview: true,
    })

    // If workspace is set via querystring, keep it during navigation
    const workspaceFromQuery = queryFromRuntime?.workspace

    const navigationPromise = isEnabled('RENDER_NAVIGATION')
      ? fetchServerPage({
          fetcher: this.fetcher,
          path: navigationRoute.path,
          query,
          workspace: workspaceFromQuery,
          deviceInfo,
          isJanusProxied,
        }).then(
          async ({
            appsEtag,
            components,
            extensions,
            matchingPage,
            messages,
            pages,
            settings,
            queryData,
          }: ParsedServerPageResponse) => {
            if (
              isConflictingLoadedComponents(components, this.state.components)
            ) {
              this.scrollTo({ top: 0, left: 0 })
              window.location.reload()
              return new Promise(() => {})
            }

            await Promise.all([
              this.hydrateApollo(queryData),
              this.fetchComponents(components, extensions),
            ])

            this.setState(
              (state) => ({
                ...state,
                appsEtag,
                components: { ...state.components, ...components },
                extensions: { ...state.extensions, ...extensions },
                loadedDevices: [deviceInfo.type],
                loadedPages: loadedPages.add(matchingPage.routeId),
                messages: { ...state.messages, ...messages },
                page: matchingPage.routeId,
                pages,
                preview: false,
                query,
                route: this.mergeRouteParams(matchingPage, transientRoute),
                settings,
              }),
              () => {
                const scrollOptions =
                  history?.action === 'POP' ? false : state.scrollOptions

                this.navigationState = { isNavigating: false }
                this.replaceRouteClass(matchingPage.routeId)
                this.sendInfoFromIframe()
                this.scrollTo(scrollOptions)
              }
            )
          }
        )
      : fetchNavigationPage({
          apolloClient,
          declarer,
          locale,
          paramsJSON,
          production,
          query: JSON.stringify(query),
          renderMajor,
          routeId,
          skipCache: false,
        }).then(
          async ({
            appsEtag,
            cacheHints,
            components,
            extensions,
            matchingPage,
            messages,
            pages,
            settings,
          }: ParsedPageQueryResponse) => {
            const updatedRoute = { ...transientRoute, ...matchingPage }
            await this.fetchComponents(components, extensions)

            this.setState(
              {
                appsEtag,
                cacheHints: mergeWith(merge, this.state.cacheHints, cacheHints),
                components: { ...this.state.components, ...components },
                extensions: { ...this.state.extensions, ...extensions },
                loadedPages: loadedPages.add(page),
                messages: { ...this.state.messages, ...messages },
                page,
                pages,
                preview: false,
                query,
                route: this.mergeRouteParams(updatedRoute, transientRoute),
                settings,
              },
              () => {
                this.navigationState = { isNavigating: false }
                this.replaceRouteClass(page)
                this.sendInfoFromIframe()
                this.scrollTo(state.scrollOptions)
              }
            )
          }
        )
    navigationPromise.finally(() => {
      if (this.navigationState.isNavigating) {
        this.navigationState = { isNavigating: false }
      }
    })
    return navigationPromise
  }

  public prefetchPage = (pageName: string) => {
    const { extensions } = this.state
    const component = extensions[pageName]?.component
    if (component) {
      const { runtime } = this.props
      const { components } = this.state
      const componentsAssetsMap = traverseComponent(components, component)
      return prefetchAssets(runtime, componentsAssetsMap)
    }
  }

  public prefetchDefaultPages = async (routeIds: string[]) => {
    const {
      runtime: { query },
    } = this.props

    const disablePrefetch =
      query &&
      DISABLE_PREFETCH_PAGES in query &&
      query[DISABLE_PREFETCH_PAGES] !== 'false'
    if (!disablePrefetch) {
      if (this.rendered) {
        console.warn(
          "prefetchDefaultPages should only be called before RenderProvider's render."
        )
        return
      }
      routeIds.forEach((routeId) => this.prefetchRoutes.add(routeId))
    }
  }

  public updateComponentAssets = (availableComponents: Components) => {
    this.setState({
      components: {
        ...this.state.components,
        ...availableComponents,
      },
    })
  }

  public fetchComponents = async (
    components: RenderRuntime['components'],
    extensions?: RenderRuntime['extensions']
  ) => {
    const { runtime } = this.props
    await fetchComponents(components, runtime, extensions)
    this.sendInfoFromIframe({ shouldUpdateRuntime: true })
  }

  public fetchComponent: RenderContext['fetchComponent'] = (component) => {
    if (!canUseDOM) {
      throw new Error('Cannot fetch components during server side rendering.')
    }

    const { runtime } = this.props
    const { components } = this.state

    const hasImplementation = !!hasComponentImplementation(component)

    if (hasImplementation) {
      return Promise.resolve()
    }

    const componentsAssetsMap = traverseComponent(components, component)

    const assetsPromise = fetchAssets(runtime, componentsAssetsMap)

    assetsPromise.then(() => {
      this.sendInfoFromIframe({ shouldUpdateRuntime: true })
    })

    return assetsPromise
  }

  public onLocaleSelected = (
    locale: string,
    domain?: string,
    callback?: (locale: string) => unknown
  ) => {
    if (locale !== this.state.culture.locale) {
      const sessionData = { public: {} }

      if (domain && domain === 'admin') {
        sessionData.public = {
          admin_cultureInfo: {
            value: locale,
          },
        }
      } else {
        sessionData.public = {
          cultureInfo: {
            value: locale,
          },
        }
      }

      return this.patchSession(sessionData)
        .then(() => {
          if (callback) return callback(locale)

          return window.location.reload()
        })
        .catch((e) => {
          console.log('Failed to fetch new locale file.')
          console.error(e)
        })
    }

    return undefined
  }

  public updateRuntime = async (options?: PageContextOptions) => {
    const {
      runtime: { renderMajor, query: queryFromRuntime, isJanusProxied },
    } = this.props
    const {
      page,
      pages: pagesState,
      production,
      culture: { locale },
      route,
      query,
      deviceInfo,
    } = this.state
    const declarer = pagesState[page] && pagesState[page].declarer
    const { pathname } = window.location
    const paramsJSON = JSON.stringify(route.params || {})

    // If workspace is set via querystring, keep it during navigation
    const workspaceFromQuery = queryFromRuntime?.workspace

    const {
      appsEtag,
      cacheHints,
      components,
      extensions,
      messages,
      pages,
      settings,
    } = isEnabled('RENDER_NAVIGATION')
      ? await fetchServerPage({
          path: route.path,
          query,
          fetcher: this.fetcher,
          workspace: workspaceFromQuery,
          deviceInfo,
          isJanusProxied,
        })
      : await fetchNavigationPage({
          apolloClient: this.apolloClient,
          declarer,
          locale,
          paramsJSON,
          path: pathname,
          production,
          query: '',
          renderMajor,
          routeId: page,
          skipCache: true,
          ...options,
        })

    await this.fetchComponents(components, extensions)

    await new Promise<void>((resolve) => {
      this.setState(
        (state) => ({
          appsEtag,
          cacheHints: isEnabled('RENDER_NAVIGATION')
            ? state.cacheHints
            : cacheHints,
          components,
          extensions: {
            ...state.extensions,
            ...extensions,
          },
          messages: {
            ...state.messages,
            ...messages,
          },
          page,
          pages,
          route,
          settings,
        }),
        resolve
      )
    })

    await this.sendInfoFromIframe()
  }

  public updateExtension = async (name: string, extension: Extension) => {
    const { extensions } = this.state

    await new Promise<void>((resolve) => {
      this.setState(
        {
          extensions: {
            ...extensions,
            [name]: extension,
          },
        },
        resolve
      )
    })

    if (name !== 'store/__overlay') {
      await this.sendInfoFromIframe()
    }
  }

  public handleSetDevice = (device: ConfigurationDevice) => {
    this.setState({ device })
  }

  public addMessages = async (newMessages: RenderRuntime['messages']) => {
    await new Promise<void>((resolve) => {
      this.setState(
        (state) => ({
          ...state,
          messages: {
            ...state.messages,
            ...newMessages,
          },
        }),
        resolve
      )
    })

    await this.sendInfoFromIframe()
  }

  private updateDevice = debounce(
    async (deviceInfo: DeviceInfo) => {
      if (!deviceInfo) {
        return
      }
      if (!this.state.loadedDevices.includes(deviceInfo.type)) {
        /** If resizing from a smaller to a larger device, keeps the current
         * blocks while the new ones are being loaded.
         * If resizing from a larger to a smaller one, can't do this because
         * the larger blocks would overflow the window and look bad.
         */
        const deviceOrder = ['desktop', 'tablet', 'phone']
        const prevDeviceType = this.state.deviceInfo.type
        const keepCurrentDevice =
          deviceOrder.indexOf(deviceInfo.type) <
          deviceOrder.indexOf(prevDeviceType)
        if (!keepCurrentDevice) {
          this.setState({ deviceInfo })
        }

        this.setState((state) => ({
          preview: true,
          loadedDevices: [...state.loadedDevices, deviceInfo.type],
        }))

        await this.updateDeviceBlocks(deviceInfo)
      }
      this.setState({
        deviceInfo,
        preview: false,
      })
    },
    200, // debounce timeout
    false // means "don't call this function immediately, wait for the debounce timeout"
  )

  public componentDidUpdate() {
    if (!equals(this.state.deviceInfo, this.props.deviceInfo)) {
      this.updateDevice(this.props.deviceInfo)
    }
  }

  public render() {
    const { children } = this.props
    const {
      culture: { locale },
      messages,
      pages,
      page,
      query,
      production,
      inspect,
    } = this.state

    const customMessages = this.getCustomMessages(locale)
    const errorMessages = getErrorPageMessages(locale)
    const mergedMessages = {
      ...errorMessages,
      ...messages,
      ...customMessages,
    }

    const component = children ? (
      React.cloneElement(children as ReactElement<any>, { query })
    ) : (
      <div className="render-provider">
        <Helmet title={pages[page] && pages[page].title} />
        <RenderPage page={page} query={query} />
      </div>
    )

    const context = this.getChildContext()

    return (
      <RenderContextProvider runtime={context}>
        <TreePathContextProvider treePath="">
          <ApolloProvider client={this.apolloClient}>
            <IntlProvider
              locale={locale}
              messages={mergedMessages}
              textComponent={Fragment}
            >
              <PrefetchContextProvider history={this.props.history}>
                <Fragment>
                  <ExtensionManager runtime={this.props.runtime} />
                  {!production && !isSiteEditorIframe && <BuildStatus />}
                  {component}
                  {isSiteEditorIframe ? (
                    <ExtensionPoint id="store/__overlay" />
                  ) : null}
                  {inspect && (
                    <Suspense fallback={null}>
                      <InspectorPopover />
                    </Suspense>
                  )}
                </Fragment>
              </PrefetchContextProvider>
            </IntlProvider>
          </ApolloProvider>
        </TreePathContextProvider>
      </RenderContextProvider>
    )
  }

  // Deprecated
  private updateMessages = (newMessages: RenderProviderState['messages']) => {
    this.setState(
      (prevState) => ({
        ...prevState,
        messages: { ...prevState.messages, ...newMessages },
      }),
      () => {
        this.sendInfoFromIframe()
      }
    )
  }

  private prefetchPages = () => {
    if (this.prefetchRoutes.size > 0) {
      setTimeout(this.execPrefetchPages, 6 * 1000)
    }
  }

  private execPrefetchPages = async () => {
    const {
      runtime,
      runtime: { renderMajor },
    } = this.props

    const {
      pages,
      culture: { locale },
    } = this.state

    const { components: defaultComponents } = await fetchDefaultPages({
      apolloClient: this.apolloClient,
      locale,
      pages,
      renderMajor,
      routeIds: Array.from(this.prefetchRoutes),
    })

    const allAssets = traverseListOfComponents(
      defaultComponents,
      Object.keys(defaultComponents)
    )

    await prefetchAssets(runtime, allAssets)
  }
}

export default withDevice<Props>(RenderProvider)

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/RenderContext.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import hoistNonReactStatics from 'hoist-non-react-statics'
import React, { ComponentType, useContext } from 'react'
import { History } from 'history'
import {
  ConfigurationDevice,
  RenderHistoryLocation,
  SetQueryOptions,
  PageContextOptions,
} from '../typings/global'
import { RenderRuntime, Components, Extension } from '../typings/runtime'
import { NavigationRouteModifier, NavigateOptions } from '../utils/pages'

export interface RenderContext
  extends Pick<
    RenderRuntime,
    | 'account'
    | 'amp'
    | 'binding'
    | 'components'
    | 'contentMap'
    | 'culture'
    | 'defaultExtensions'
    | 'deviceInfo'
    | 'emitter'
    | 'extensions'
    | 'hints'
    | 'inspect'
    | 'messages'
    | 'page'
    | 'pages'
    | 'platform'
    | 'preview'
    | 'production'
    | 'publicEndpoint'
    | 'query'
    | 'renderMajor'
    | 'rootPath'
    | 'route'
    | 'workspace'
  > {
  addMessages: (newMessages: RenderRuntime['messages']) => Promise<void>
  addNavigationRouteModifier: (modifier: NavigationRouteModifier) => void
  ensureSession: () => Promise<void>
  fetchComponent: (component: string) => Promise<unknown>
  fetchComponents: (
    components: RenderRuntime['components'],
    extensions?: RenderRuntime['extensions']
  ) => Promise<void>
  getSettings: (app: string) => any
  goBack: () => void
  device: string
  history: History | null
  navigate: (options: NavigateOptions) => boolean
  onPageChanged: (location: RenderHistoryLocation) => void
  patchSession: (data?: any) => Promise<void>
  prefetchDefaultPages: (routeIds: string[]) => Promise<void>
  prefetchPage: (name: string) => void
  setDevice: (device: ConfigurationDevice) => void
  setQuery: (query?: Record<string, any>, options?: SetQueryOptions) => boolean
  updateComponentAssets: (availableComponents: Components) => void
  updateExtension: (name: string, extension: Extension) => Promise<void>
  updateRuntime: (options?: PageContextOptions) => Promise<void>
  navigationRouteModifiers: Set<NavigationRouteModifier>
}

export interface RenderContextProps {
  runtime: RenderContext
}

export interface EmitterProps {
  __emitter: RenderContext['emitter']
}

export const RenderContext = React.createContext<RenderContext>({} as any)
RenderContext.displayName = 'RenderContext'

export const RenderContextProvider: React.FC<RenderContextProps> = ({
  children,
  runtime,
}) => (
  <RenderContext.Provider value={runtime}>{children}</RenderContext.Provider>
)
RenderContextProvider.displayName = 'RenderContextProvider'

export const useRuntime = (): RenderContext => {
  return useContext(RenderContext)
}

export const withRuntimeContext = <TOriginalProps extends {} = {}>(
  Component: ComponentType<TOriginalProps & RenderContextProps>
): ComponentType<TOriginalProps> => {
  const WithRuntimeContext = (props: TOriginalProps) => {
    const runtime = useRuntime()
    return <Component {...props} runtime={runtime} />
  }
  WithRuntimeContext.displayName = `withRuntimeContext(${
    Component.displayName || Component.name || 'Component'
  })`
  WithRuntimeContext.WrappedComponent = Component
  return hoistNonReactStatics<TOriginalProps, RenderContextProps>(
    WithRuntimeContext,
    Component
  )
}

export const withEmitter = <TOriginalProps extends {} = {}>(
  Component: ComponentType<TOriginalProps & EmitterProps>
): ComponentType<TOriginalProps> => {
  const WithEmitter = (props: TOriginalProps) => {
    const { emitter } = useRuntime()
    return <Component {...props} __emitter={emitter} />
  }
  WithEmitter.displayName = `withEmitter(${
    Component.displayName || Component.name || 'Component'
  })`
  WithEmitter.WrappedComponent = Component
  return hoistNonReactStatics<TOriginalProps, EmitterProps>(
    WithEmitter,
    Component
  )
}

export default RenderContext

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/LazyRender.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { FunctionComponent, useRef, useState } from 'react'
import { useOnView } from '../hooks/viewDetection'

interface Props {
  height?: number
  offset?: number
  debug?: boolean
}

const LazyRender: FunctionComponent<Props> = ({
  children,
  height = 400,
  offset = 300,
}) => {
  const ref = useRef(null)
  const [hasBeenViewed, setHasBeenViewed] = useState(false)

  useOnView({
    ref,
    onView: () => {
      setHasBeenViewed(true)
    },
    once: true,
    initializeOnInteraction: true,
  })

  if (hasBeenViewed) {
    return <>{children}</>
  }

  return (
    <div
      style={{
        position: 'relative',
        width: '100%',
        height,
      }}
      className="vtex-render__lazy-container"
    >
      <div
        ref={ref}
        style={{
          position: 'relative',
          width: '100%',
          top: -offset,
          height: '100%',
          paddingBottom: offset * 2,
          boxSizing: 'content-box',
        }}
      />
    </div>
  )
}

export default LazyRender

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/Helmet.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import { Helmet } from 'react-helmet'

// We need this indirection so its types are exported
export default Helmet

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/useRuntime.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { useRuntime } from './RenderContext'
import type { RenderContext } from './RenderContext'

type PublicRuntime = Pick<
  RenderContext,
  | 'account'
  | 'binding'
  | 'culture'
  | 'deviceInfo'
  | 'getSettings'
  | 'hints'
  | 'history'
  | 'navigate'
  | 'page'
  | 'pages'
  | 'route'
  | 'production'
  | 'query'
  | 'renderMajor'
  | 'rootPath'
  | 'setQuery'
  | 'workspace'
>

const usePublicRuntime = useRuntime as () => PublicRuntime

export default usePublicRuntime

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/Block.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import ExtensionPoint from './ExtensionPoint/index'

// We need this indirection so its types are exported
export default ExtensionPoint

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/useChildBlock.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import { useExtension } from '../hooks/extension'

export interface ChildBlockType {
  id: string
}

export interface Block {
  props: Record<string, any>
}

function useChildBlock(childBlock: ChildBlockType): Block | null {
  if (typeof childBlock === 'string') {
    throw new Error(
      `You are passing a string as a parameter to useChildBlock ("${childBlock}"). You should pass an object like {id: "${childBlock}"}.`
    )
  }

  const { id } = childBlock

  if (!id) {
    throw new Error('The id you are sending to useChildBlock is empty')
  }

  const extension = useExtension({ children: id })

  // We are explicitly not exposing the private API here
  return extension ? { props: extension.props } : null
}

export default useChildBlock

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/RenderPage.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { FC } from 'react'
import ExtensionPoint from './ExtensionPoint'
import MaybeContext from './MaybeContext'
import { useRuntime } from './RenderContext'

interface Props {
  page: string
  query?: Record<string, string>
}

const RenderPage: FC<Props> = (props) => {
  const runtime = useRuntime()
  const { page, query } = props
  const {
    route: { params },
  } = runtime

  return (
    <MaybeContext
      nestedPage={page}
      query={query}
      params={params}
      runtime={runtime}
    >
      <ExtensionPoint id={page} query={query} params={params} {...props} />
    </MaybeContext>
  )
}

export default RenderPage

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/LazyImages.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { useContext, FC } from 'react'
import styles from './LazyImages.css'
import { Helmet } from 'react-helmet'

interface LazyImagesContext {
  lazyLoad: boolean
  /** "native" uses the attribute "loading=lazy", which is good but only works
   * on Chrome. lazysizes is a JS plugin already provided by render-runtime,
   * with broader support, but the behaviour might be a little worse at times */
  method: 'native' | 'lazysizes'
}

const LazyImagesContext = React.createContext<LazyImagesContext>({
  lazyLoad: false,
  method: 'lazysizes',
})

interface LazyImagesProps {
  lazyLoad?: boolean
  experimentalMethod?: LazyImagesContext['method']
}

const LazyImages: FC<LazyImagesProps> = ({
  children,
  lazyLoad = true,
  experimentalMethod = 'lazysizes',
}) => {
  return (
    <LazyImagesContext.Provider
      value={{ lazyLoad, method: experimentalMethod }}
    >
      {children}
    </LazyImagesContext.Provider>
  )
}

const useLazyImagesContext = () => {
  const value = useContext(LazyImagesContext)

  return value
}

interface MaybeLazyImageProps {
  createElement: typeof React.createElement
  imageProps: Record<string, any>
}

const MaybeLazyImage: FC<MaybeLazyImageProps> = ({
  createElement = React.createElement,
  imageProps,
}) => {
  const { lazyLoad, method } = useLazyImagesContext()

  if (lazyLoad) {
    let newImageProps = imageProps

    switch (method) {
      case 'native':
        newImageProps = {
          ...imageProps,
          loading: 'lazy',
        }
        break
      case 'lazysizes':
        /** adds `lazyload` class to enable lazysizes, and moves the image URI
         * from src to data-src. `styles.lazyload` is used to hide the "broken image"
         * symbol while the image hasn't been loaded */
        newImageProps = {
          ...imageProps,
          // explicity key because we need react to re-render the whole img element
          // in case the src changes (i.e: product-summary + sku)
          key: imageProps.src,
          className: `lazyload ${imageProps.className ?? ''} ${
            styles.lazyload
          }`,
          src: undefined,
          'data-src': imageProps.src,
          loading: 'lazy',
        }
        break
    }
    return createElement.apply(React, ['img', newImageProps])
  }

  const isPreloaded = String(imageProps['data-vtex-preload']) === 'true'

  // If it's preloaded, render the image along with a Helmet that adds preload
  if (isPreloaded) {
    return (
      <>
        <Helmet
          link={[
            {
              rel: 'preload',
              as: 'image',
              href: imageProps.src,
              crossOrigin: imageProps.crossOrigin,
            },
          ]}
        />
        {createElement.apply(React, ['img', imageProps])}
      </>
    )
  }

  // Otherwise, just render the image
  return createElement.apply(React, ['img', imageProps])
}

export { LazyImages, MaybeLazyImage, useLazyImagesContext }

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/Loading.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'

import Preview from './Preview'

import { useExtension } from '../hooks/extension'

const Loading = () => {
  const extension = useExtension()

  if (!extension) {
    return null
  }

  const { preview } = extension

  if (!preview) {
    return null
  }

  return <Preview extension={extension} />
}

export default Loading

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/TrackEventsWrapper.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import { forEach } from 'ramda'
import { PureComponent } from 'react'
import ReactDOM from 'react-dom'

interface Props {
  events?: string[]
  id?: string
}

const sendEvent = (id: string, event: string) => {
  window.postMessage(
    {
      pageComponentInteraction: {
        blockId: id,
        interactionType: event,
        namespace: 'renderRuntime',
      },
    },
    '*'
  )
}

class TrackEventsWrapper extends PureComponent<Props> {
  public eventsHandler: Record<string, () => void> = {}

  public componentDidMount() {
    const { id, events } = this.props
    if (events && events.length && id) {
      // eslint-disable-next-line react/no-find-dom-node
      const element = ReactDOM.findDOMNode(this)
      if (element && element.addEventListener) {
        forEach((event) => {
          this.eventsHandler[event] = () => sendEvent(id, event)
          element.addEventListener(event, this.eventsHandler[event])
        }, events)
      }
    }
  }

  public componentWillUnmount() {
    const { id, events } = this.props
    if (events && events.length && id) {
      // eslint-disable-next-line react/no-find-dom-node
      const element = ReactDOM.findDOMNode(this)
      if (element && element.addEventListener) {
        forEach(
          (event) =>
            element.removeEventListener(event, this.eventsHandler[event]),
          events
        )
      }
    }
  }

  public render() {
    const { children } = this.props
    return children
  }
}

export default TrackEventsWrapper

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/MaybeContext.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { useMemo, Fragment, FC } from 'react'
import ComponentLoader from '../components/ExtensionPoint/ComponentLoader'
import { RenderContextProps } from './RenderContext'
import { ExtensionPoint } from '../core/main'

interface Props extends RenderContextProps {
  nestedPage: string
  params?: Record<string, any>
  query?: Record<string, string>
}

const useContextComponent = ({
  nestedPage,
  query,
  params,
  runtime,
}: Props & RenderContextProps) => {
  const { extensions } = runtime

  const { context, before, after, props: pageProps } =
    extensions[nestedPage] ?? {}

  const pageContextProps = pageProps && pageProps.context

  /** Allows rendering header/footer if the Context component is loading,
   * by exposing them to ExtensionPointComponent via props.
   * TODO:This is a bit of a hack. The header/footer should probably be decoupled
   * from the context, but doing so introduces bugs on some stores. */
  const beforeElements =
    before &&
    before.map((beforeId) => (
      <ExtensionPoint id={beforeId} key={beforeId} treePath={nestedPage} />
    ))

  const afterElements =
    after &&
    after.map((afterId) => (
      <ExtensionPoint id={afterId} key={afterId} treePath={nestedPage} />
    ))

  const contextProps = useMemo(() => {
    if (!context) {
      return undefined
    }
    return {
      ...pageContextProps,
      beforeElements,
      afterElements,
      nextTreePath: nestedPage,
      params,
      query,
      ...context.props,
    }
  }, [
    context,
    pageContextProps,
    beforeElements,
    afterElements,
    nestedPage,
    params,
    query,
  ])
  const contextComponent = context ? context.component : undefined
  return [contextProps, contextComponent]
}

const MaybeContext: FC<Props> = ({
  children,
  nestedPage,
  query,
  params,
  runtime,
}) => {
  const [contextProps, contextComponent] = useContextComponent({
    nestedPage,
    query,
    params,
    runtime,
  })

  return contextComponent ? (
    <ComponentLoader
      component={contextComponent}
      props={contextProps}
      runtime={runtime}
      treePath={nestedPage}
      hydration="always"
    >
      {children}
    </ComponentLoader>
  ) : (
    <Fragment>{children}</Fragment>
  )
}

export default MaybeContext

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/NoSSR.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, {
  useState,
  useEffect,
  useLayoutEffect,
  FunctionComponent,
} from 'react'

const useEnhancedEffect =
  typeof window !== 'undefined' ? useLayoutEffect : useEffect

const useSSR = () => {
  const [isCSRAvailable, setCSR] = useState(false)

  useEnhancedEffect(() => {
    setCSR(true)
  }, [])

  return !isCSRAvailable
}

interface Props {
  onSSR?: React.ReactNode
}

const NoSSR: FunctionComponent<Props> = ({ children, onSSR }) => {
  const isSSR = useSSR()

  return <>{isSSR ? onSSR : children}</>
}

export default NoSSR

export { useSSR }

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/BuildStatus.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { Component } from 'react'
import { RenderContextProps, withRuntimeContext } from './RenderContext'

interface State {
  animateOut: boolean
  status: string | null
  anchor: 'left' | 'right'
  interrupted: boolean | null
}

class BuildStatus extends Component<RenderContextProps, State> {
  private animateOutHandle!: number
  private hideHandle!: number

  public constructor(props: RenderContextProps) {
    super(props)

    this.state = {
      animateOut: false,
      status: null,
      interrupted: null,
      anchor: 'left',
    }
  }

  public clearTimeouts = () => {
    clearTimeout(this.animateOutHandle)
    clearTimeout(this.hideHandle)
  }

  public hideWithDelay = (delayMillis: number) => {
    this.animateOutHandle = window.setTimeout(
      () => this.setState({ animateOut: true }),
      delayMillis
    )
    this.hideHandle = window.setTimeout(
      () =>
        this.setState({ status: null, interrupted: null, animateOut: false }),
      delayMillis + 300
    )
  }

  private handleMouseOver = () => {
    this.setState((state) => ({
      anchor: state.anchor === 'left' ? 'right' : 'left',
    }))
  }

  public updateStatus = (status: string) => {
    // After we started a reload, the UI shouldn't move.
    if (this.state.status === 'reload') {
      return
    }

    this.setState({ status, animateOut: false })
    this.clearTimeouts()

    if (status === 'success' || status === 'hmr:success') {
      const delay = status === 'success' ? 2000 : 0
      this.hideWithDelay(delay)
    }
  }

  public updateInterrupted = () => {
    this.clearTimeouts()
    this.setState({ interrupted: true, animateOut: false })
    const delay = 2000
    this.hideWithDelay(delay)
  }

  public subscribeToStatus = () => {
    const { emitter } = this.props.runtime
    emitter.addListener('build.status', this.updateStatus)
  }

  public unsubscribeToStatus = () => {
    const { emitter } = this.props.runtime
    emitter.removeListener('build.status', this.updateStatus)
  }

  public subscribeToLinkInterrupted = () => {
    const { emitter } = this.props.runtime
    emitter.addListener('link_interrupted', this.updateInterrupted)
  }

  public unsubscribeToLinkInterrupted = () => {
    const { emitter } = this.props.runtime
    emitter.removeListener('link_interrupted', this.updateInterrupted)
  }

  public componentDidMount() {
    this.subscribeToStatus()
    this.subscribeToLinkInterrupted()
  }

  public componentWillUnmount() {
    this.unsubscribeToStatus()
    this.unsubscribeToLinkInterrupted()
    this.clearTimeouts()
  }

  private renderLoading = () => (
    <svg
      width="26px"
      height="26px"
      viewBox="0 0 100 100"
      preserveAspectRatio="xMidYMid"
    >
      <style>{`
        @keyframes build-status-rotate {
            0% {
              transform: rotate(0);
            }
            100% {
              transform: rotate(360deg);
            }
          }

          .build-status-rotate {
            transform-origin: 50px 50px;
            animation: build-status-rotate 0.7s infinite linear;
          }

        `}</style>
      <g>
        <circle
          cx="50"
          opacity="0.4"
          cy="50"
          fill="none"
          stroke="#F71963"
          r="40"
          strokeWidth="14"
        />
        <circle
          cx="50"
          cy="50"
          fill="none"
          stroke="#F71963"
          r="40"
          strokeDasharray="60 900"
          strokeWidth="12"
          strokeLinecap="round"
          className="build-status-rotate"
        />
      </g>
    </svg>
  )

  public render() {
    const { status, animateOut, anchor, interrupted } = this.state

    if (status === null && interrupted === null) {
      return null
    }

    const className = `build-status ${status} z-max fixed animated pa3 bg-near-white br3 rebel-pink flex items-center shadow-4 ${
      animateOut ? 'fadeOut' : 'fadeIn'
    }`

    const message = interrupted
      ? 'Link interrupted'
      : status === 'fail'
      ? 'Oops! Build failed. Check your terminal for more information'
      : status === 'reload'
      ? 'Performing full reload'
      : null

    return (
      // eslint-disable-next-line jsx-a11y/mouse-events-have-key-events
      <div
        aria-hidden
        className={className}
        style={{
          top: '12px',
          [anchor]: '12px',
          animationDuration: '0.2s',
          opacity: 0.8,
        }}
        onMouseOver={this.handleMouseOver}
      >
        {message ? <p className="ma2">{message}</p> : this.renderLoading()}
      </div>
    )
  }
}

export default withRuntimeContext<{}>(BuildStatus)

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/withRuntimeContext.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import { withRuntimeContext } from './RenderContext'

export default withRuntimeContext

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/ErrorBoundary.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { ErrorInfo, FunctionComponent } from 'react'
import ErrorDisplay from './ExtensionPoint/ErrorDisplay'
import { useRuntime } from './RenderContext'
import type { RenderContext } from './RenderContext'
import { isAdmin } from '../utils/isAdmin'
import { captureException } from '@sentry/react'
import { CustomAdminTags } from '../o11y/types'
import ErrorPage from './ErrorPage/ErrorPage'

interface Props {
  runtime: RenderContext
}

class ErrorBoundary extends React.Component<Props> {
  public state = {
    error: undefined,
    errorInfo: undefined,
  }

  public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    this.setState({
      error,
      errorInfo,
    })

    if (!isAdmin()) return

    const tags: CustomAdminTags = {
      admin_render_runtime_page: 'ErrorBoundary',
    }

    captureException(this.state, { tags })
  }

  public render() {
    const { production } = this.props.runtime

    const { error, errorInfo } = this.state
    if (error) {
      if (!production) {
        return <ErrorDisplay error={error} errorInfo={errorInfo} />
      }

      if (isAdmin()) {
        return <ErrorPage />
      }

      return null
    }

    return this.props.children
  }
}

const ErrorBoundaryWithContext: FunctionComponent = ({ children }) => {
  const runtime = useRuntime()

  return <ErrorBoundary runtime={runtime}>{children}</ErrorBoundary>
}

export default ErrorBoundaryWithContext

export const withErrorBoundary = <P extends object>(
  Component: React.ComponentType<P>
) => {
  const WithErrorBoundary = (props: P) => {
    const runtime = useRuntime()

    return (
      <ErrorBoundary runtime={runtime}>
        <Component {...props} />
      </ErrorBoundary>
    )
  }

  const displayName = Component.displayName || Component.name

  if (displayName) {
    WithErrorBoundary.displayName = `withErrorBoundary(${displayName})`
  }

  return WithErrorBoundary
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/components/LoadingContext.tsx from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, {
  useContext,
  useCallback,
  useMemo,
  useRef,
  ComponentType,
  FunctionComponent,
  useEffect,
} from 'react'
import { canUseDOM } from 'exenv'

import GenericPreview from './Preview/GenericPreview'

interface LoadingContext {
  setLoading: (treePath: string, isLoading: boolean) => void
  isParentLoading?: boolean
}

const LoadingContext = React.createContext<LoadingContext>({
  setLoading: () => {},
  isParentLoading: false,
})

LoadingContext.displayName = 'LoadingContext'

const LoadingContextProvider = LoadingContext.Provider

interface LoadingState {
  components: { [key: string]: boolean }
}

const displayContent = (
  contentRef: React.RefObject<HTMLElement>,
  loaderRef: React.RefObject<HTMLElement>
) => {
  if (contentRef.current) {
    contentRef.current.style.position = ''
    contentRef.current.style.width = ''
    contentRef.current.style.opacity = ''
  }
  if (loaderRef.current) {
    loaderRef.current.style.display = 'none'
  }
}

const displayLoader = (
  contentRef: React.RefObject<HTMLElement>,
  loaderRef: React.RefObject<HTMLElement>
) => {
  if (contentRef.current) {
    contentRef.current.style.position = 'absolute'
    contentRef.current.style.width = '100%'
    contentRef.current.style.opacity = '0'
  }
  if (loaderRef.current) {
    loaderRef.current.style.display = ''
  }
}

/** TODO: LoadingWrapper is in the end a makeshift Suspense.
 * Should probably be replaced in the future. */
const LoadingWrapper: FunctionComponent = ({ children }) => {
  /* Uses Ref instead of state to prevent rerenders */
  const state = useRef<LoadingState>({ components: {} })

  /* A parent component can include a LoadingContext to tell its
   * children that it is loading data. */
  const { isParentLoading } = useContext(LoadingContext)

  const contentContainer = useRef<HTMLDivElement>(null)
  const loaderContainer = useRef<HTMLDivElement>(null)

  const loadingTimeout = useRef<number | null>(null)
  const loadingComplete = useRef(false)

  const updateLoading = useCallback(() => {
    const areComponentsLoading = Object.values(state.current.components).some(
      Boolean
    )

    const isLoading = isParentLoading || areComponentsLoading

    if (!isLoading) {
      displayContent(contentContainer, loaderContainer)
      loadingComplete.current = true
    } else {
      if (!loadingComplete.current && loadingTimeout.current !== null) {
        window.clearTimeout(loadingTimeout.current)
        loadingTimeout.current = null
      }

      if (isParentLoading && loadingComplete.current) {
        loadingComplete.current = false
        displayLoader(contentContainer, loaderContainer)
      }
    }
  }, [isParentLoading])

  const setLoading = useCallback(
    (treePath, loading) => {
      if (loadingComplete.current) {
        return
      }

      state.current.components[treePath] = loading

      updateLoading()
    },
    [updateLoading]
  )

  useEffect(() => {
    updateLoading()
  }, [isParentLoading, updateLoading])

  const value = useMemo(
    () => ({
      setLoading,
    }),
    [setLoading]
  )

  const isSSR = !canUseDOM

  const returnValue = useMemo(
    () => (
      /** Renders both the content and the loader, and hides them via changing the
       * style via their refs. This is done for two reasons:
       * 1: to make the content execute its code while it is loading, because the
       * content can in turn require more components to be loaded, thus keeping
       * the loading state for longer.
       * 2: To prevent re-rendering when the loading state changes, which seemed
       * to cause some bugs. */
      <LoadingContextProvider value={value}>
        <div
          // Content container
          ref={contentContainer}
          suppressHydrationWarning
          style={{
            opacity: isSSR ? 1 : 0,
            position: isSSR ? 'unset' : 'absolute',
            width: isSSR ? 'unset' : '100%',
          }}
        >
          {children}
        </div>
        <div
          // Loader container
          ref={loaderContainer}
          suppressHydrationWarning
          style={{ display: isSSR ? 'none' : 'unset' }}
        >
          {/** TODO: Use a better preview in the future */}
          <GenericPreview />
        </div>
      </LoadingContextProvider>
    ),
    [children, isSSR, value]
  )

  return returnValue
}

const useLoadingContext = () => {
  return useContext(LoadingContext)
}

const withLoading = <T extends {}>(Component: ComponentType<T>) => {
  const EnhancedComponent: FunctionComponent<T> = (props) => {
    const isSSR = !canUseDOM
    const { setLoading } = useLoadingContext()

    if (isSSR) {
      return <Component {...props} setLoading={() => {}} />
    }

    return <Component {...props} setLoading={setLoading} />
  }

  EnhancedComponent.displayName = `WithLoading(${
    Component.displayName || Component.name || 'Component'
  })`

  return EnhancedComponent
}

export {
  LoadingWrapper,
  useLoadingContext,
  withLoading,
  LoadingContextProvider,
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/o11y/ctx.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { AdminTags } from './types'

interface RuntimeInfo {
  account: string | null
  workspace: string | null
  route: { path: string | null; blockId: string | null }
  culture: { locale: string | null }
  production: boolean | null
  loadedDevices: Array<string | null>
}

/**
 * Infer the runtime information from the window.location object.
 */
export function inferRuntimeInfo(): Partial<RuntimeInfo> {
  const { host = '' } = window.location
  let account = ''
  let workspace = ''

  if (!host.includes('--')) {
    account = host.split('.')[0]
    workspace = 'master'
  } else {
    workspace = host.split('--')[0]
    account = host.split('--')[1].split('.')[0]
  }

  const { pathname } = window.location

  const locale =
    navigator?.language ?? Intl.DateTimeFormat().resolvedOptions().locale

  return {
    account,
    workspace,
    route: { path: pathname, blockId: null },
    culture: { locale },
  }
}

/**
 * Gets the Runtime information from the global __RUNTIME__ object
 * injected by Render Server, if available, otherwise infer the runtime
 * information from the window.location.
 */
export function getRuntimeInfo(): RuntimeInfo & { runtimeAvailable: boolean } {
  const runtime = window?.__RUNTIME__ ?? inferRuntimeInfo()

  const {
    account = null,
    workspace = null,
    culture: { locale } = { locale: null },
    route: { path, blockId } = { path: null, blockId: null },
    loadedDevices = null,
    production = null,
  } = runtime

  return {
    account,
    workspace,
    culture: { locale },
    route: { path, blockId },
    loadedDevices,
    production,
    runtimeAvailable: !!window?.__RUNTIME__,
  }
}

/**
 * Retrieves VTEX IO context and adapt it to format expected
 * by Sentry for the Admin.
 */
export function getIOContext(): AdminTags {
  const runtime = window?.__RUNTIME__ ?? inferRuntimeInfo()

  const {
    account = null,
    workspace = null,
    culture: { locale } = { locale: null },
    route: { path, blockId } = { path: null, blockId: null },
    loadedDevices = null,
    production = null,
  } = runtime

  return {
    admin_account: account,
    admin_workspace: workspace,
    admin_locale: locale,
    admin_path: path,
    admin_app_block: blockId, // ex. "vtex.admin-home@3.x:admin.app.home"
    admin_device: Array.isArray(loadedDevices)
      ? loadedDevices[0]
      : loadedDevices,
    admin_production: production,
    admin_runtime_available: !!window?.__RUNTIME__,
  }
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/o11y/extractExtra.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
/**
 * This method extracts keys from objects iteratively
 * and adpat them to the format required by Sentry to log
 * events from the Admin by following our conventions.
 *
 * Sentry doesn't log all the keys from an object
 * when an error is captured, so we need to extract
 * the keys and values from the error object and
 * adapt them to the format required by Sentry ourselves
 * in order to make them indexable and searchable.
 *
 * This helps us aggregate and filter errors in Sentry by
 * dynamically generating keys.
 */
export function extractExtra(err: any) {
  try {
    const extra: any = {}

    return iterate(err, extra, 0, new Set())
  } catch (error) {
    return {}
  }
}

/**
 * This function is recursive and will iterate over the
 * object's keys and values to extract them and adapt
 * them to the format required by Sentry.
 */
function iterate(
  err: any,
  extra: any,
  iterationNumber: number,
  seen: Set<any>
) {
  if (typeof err === 'object') {
    // We don't want to rely on the browser's ability
    // to handle memory leaks when dealing with circular
    // references, so we need to keep track of the keys
    // we've already seen to avoid infinite loops.
    if (seen.has(err)) {
      throw new Error('Circular reference detected')
    }
    seen.add(err)

    for (const key in err) {
      if (typeof err[key] === 'object') {
        const updatedExtra = iterate(err[key], extra, iterationNumber + 1, seen)
        extra = { ...extra, ...updatedExtra }
      } else {
        if (typeof err[key] !== 'function') {
          const value = err[key]
          const tagKey = `admin_extra_${standardizeKey(key)}${iterationNumber}`

          if (tagKey.length >= 32) {
            // Skip. Sentry doesn't allow keys longer than 32 characters.
            // The keys that matter the most are shortened on the standardizeKey function.
            continue
          }

          // Split values that are too long into as many parts as necessary to fit
          // Sentry's limit of 200 characters per value
          if (value.length >= 200) {
            const chunks = []
            for (let chunk = 0; chunk < value.length; chunk += 199) {
              chunks.push(value.substr(chunk, 199))
            }

            for (let chunk = 0; chunk < chunks.length; chunk++) {
              extra[`${tagKey}_` + chunk] = chunks[chunk]
            }
          } else {
            extra[tagKey] = value
          }
        }
      }

      seen.delete(err)
    }
  } else {
    return err
  }

  return extra
}

/**
 * This function standardizes the keys extracted from the error object
 * to the format required by Sentry (snake_case, lowercase, short).
 */
function standardizeKey(str: string) {
  return str
    .replace(/-/g, '_')
    .toLowerCase()
    .trim()
    .replace('graphql', 'gql')
    .replace('message', 'msg')
    .replace('request', 'req')
    .replace('response', 'res')
    .replace('isaxioserror', 'axios')
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of render-runtime/react/o11y/instrument.ts from the render-runtime VTEX app.

[Assistant]
Here's the implementation:

```typescript
import * as Sentry from '@sentry/react'
import { isAdmin } from '../utils/isAdmin'
import { getIOContext } from './ctx'

if (isAdmin()) {
  Sentry.init({
    dsn:
      'https://2fac72ea180d48ae9bf1dbb3104b4000@o191317.ingest.us.sentry.io/1292015',
    integrations: [Sentry.replayIntegration()],

    // Set tracesSampleRate to 0.1 to capture 10%
    // of transactions for tracing.
    tracesSampleRate: 0.1,

    // Set `tracePropagationTargets` to control for which URLs trace propagation should be enabled
    tracePropagationTargets: [
      /^\//,
      /^(https?:\/\/)?([a-z0-9]+[.])*myvtex\.com/,
    ],

    // Capture Replay for 0% of all sessions,
    // plus for 50% of sessions with an error
    replaysSessionSampleRate: 0,
    replaysOnErrorSampleRate: 0.5,

    beforeSend: (event) => {
      const ctx = getIOContext()

      // Must check with false, otherwise default null's
      // value leads to data mistakenly not sent to Sentry,
      // which can occur if somehow we can't infer whether
      // the apps are running under a production or development
      // environment.
      if (ctx.admin_production === false) {
        const params = new URL(document?.location?.toString())?.searchParams
        const shouldLog = params.get('forceLogs')

        if (shouldLog === 'true') {
          return makeEventWithCtx(event, ctx)
        }

        return null
      }

      return makeEventWithCtx(event, ctx)
    },
  })
}

function makeEventWithCtx(event: any, ctx: any) {
  const eventWithCtx = {
    ...event,
    tags: {
      ...event?.tags,
      ...ctx,
    },
  }

  return eventWithCtx
}

```