utils.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import BN from 'bn.js'
  2. import fetch from 'cross-fetch'
  3. import parseHtml, { HTMLElement } from 'node-html-parser'
  4. import { DataObjectFieldsFragment } from './api/__generated__/sdk'
  5. import { AppData, MetaTags } from './types'
  6. const DISTRIBUTOR_ASSET_PATH = 'api/v1/assets'
  7. export const joinUrlFragments = (...fragments: string[]) => {
  8. const strippedFragments = fragments.map((f) => f.replace(/^\/|\/$/, ''))
  9. return strippedFragments.join('/')
  10. }
  11. export const generateAssetUrl = (asset: DataObjectFieldsFragment) => {
  12. const workingBuckets = asset.storageBag.distributionBuckets.filter((bucket) => bucket.distributing)
  13. const distributorsEndpoints = workingBuckets.reduce((acc, bucket) => {
  14. const endpoints = bucket.operators
  15. .filter((operator) => !!operator.metadata?.nodeEndpoint)
  16. .map((operator) => operator.metadata?.nodeEndpoint) as string[]
  17. return [...acc, ...endpoints]
  18. }, [] as string[])
  19. const assetIdBn = new BN(asset.id)
  20. const endpointsCountBn = new BN(distributorsEndpoints.length)
  21. const distributorIndex = assetIdBn.mod(endpointsCountBn).toNumber()
  22. const endpoint = distributorsEndpoints[distributorIndex]
  23. return joinUrlFragments(endpoint, DISTRIBUTOR_ASSET_PATH, asset.id)
  24. }
  25. export const getEnvVariable = (varName: string, required?: boolean) => {
  26. if (!process.env[varName] && required) {
  27. // eslint-disable-next-line no-console
  28. console.error(`Missing required ${varName} env variable`)
  29. process.exit(1)
  30. }
  31. return process.env[varName] || ''
  32. }
  33. export const generateMetaHtml = (tags: MetaTags, addHelmetAttr = false) => {
  34. return Object.entries(tags)
  35. .map(
  36. ([name, content]) =>
  37. `<meta name="${name}" property="${name}" content="${content}" ${
  38. addHelmetAttr ? 'data-react-helmet="true"' : ''
  39. }>`
  40. )
  41. .join('\n')
  42. }
  43. export const applyMetaTagsToHtml = (html: HTMLElement, metaTags: MetaTags) => {
  44. // remove already present meta tags
  45. const metaTagsLookup = Object.keys(metaTags).reduce<Record<string, boolean>>((acc, key) => {
  46. acc[key.toLowerCase()] = true
  47. return acc
  48. }, {})
  49. const metaTagsToRemove = html.querySelectorAll('meta').filter((metaTag) => {
  50. const name = metaTag.getAttribute('name') || metaTag.getAttribute('property')
  51. return name && metaTagsLookup[name.toLowerCase()]
  52. })
  53. metaTagsToRemove.forEach((metaTag) => metaTag.remove())
  54. // add new meta tags
  55. const head = html.querySelector('head')
  56. const metaTagsHtml = generateMetaHtml(metaTags)
  57. head?.insertAdjacentHTML('beforeend', metaTagsHtml)
  58. }
  59. export const applySchemaTagsToHtml = (html: HTMLElement, schemaTags: string) => {
  60. const head = html.querySelector('head')
  61. head?.insertAdjacentHTML('beforeend', schemaTags)
  62. }
  63. export const fetchHtmlAndAppData = async (url: string): Promise<[HTMLElement, AppData]> => {
  64. // fetch and parse html
  65. const response = await fetch(url)
  66. const html = await response.text()
  67. const parsedHtml = parseHtml(html)
  68. const getMetaTagContent = (name: string) => {
  69. const metaTag = parsedHtml.querySelector(`meta[name="${name}"]`)
  70. return metaTag?.getAttribute('content')
  71. }
  72. // extract app data from the HTML
  73. const siteName = getMetaTagContent('og:site_name')
  74. if (!siteName) {
  75. throw new Error('Missing site name')
  76. }
  77. const orionUrl = getMetaTagContent('atlas:orion_url')
  78. if (!orionUrl) {
  79. throw new Error('Missing Orion URL in fetched HTML')
  80. }
  81. const appData: AppData = {
  82. name: siteName,
  83. orionUrl: orionUrl,
  84. twitterId: getMetaTagContent('twitter:site'),
  85. yppOgTitle: getMetaTagContent('atlas:ypp_og_title'),
  86. yppOgDescription: getMetaTagContent('atlas:ypp_og_description'),
  87. yppOgImage: getMetaTagContent('atlas:ypp_og_image'),
  88. }
  89. return [parsedHtml, appData]
  90. }