Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save 0xdevalias/0e723b6c46fec7947a3119de9dd5045d to your computer and use it in GitHub Desktop.
Save 0xdevalias/0e723b6c46fec7947a3119de9dd5045d to your computer and use it in GitHub Desktop.
Random Facebook React / Relay JavaScript Snippets

Random Facebook React / Relay JavaScript Snippets


In Relay DevTools, while viewing:

Some interesting paths:

  • Story -> .message (TextWithEntities) -> __ref (client: 2:message) -> .text
  • Story -> .message(location:"homepage_stream") (TextWithEntities) -> __ref (client: 2:message(location:"homepage_stream")) -> .text
findAllReactElements(({ props, fiber, element }) => {
  return props?.children?.props?.hasOwnProperty('feedEdge')
}).map(el => {
  const props = getReactProps(el)
  return props?.children?.props?.feedEdge?.node
})

// __id
// __typename: Story
// This is basically a wrapper around a Map
__RELAY_DEVTOOLS_HOOK__.environments.get(1).getStore().getSource()

// This gives us all of the record IDs
__RELAY_DEVTOOLS_HOOK__.environments.get(1).getStore().getSource().getRecordIDs()

// We can do a crude search through them if we want
__RELAY_DEVTOOLS_HOOK__.environments.get(1).getStore().getSource().getRecordIDs().filter(id => id.toLowerCase().includes('story'))

// Or access a record by it's ID
__RELAY_DEVTOOLS_HOOK__.environments.get(1).getStore().getSource().get('UzpfSUZTOjE6LTIxNTE1MTA2NzU0ODcxMTY2NDI6ZUp3VG0zbjIrTjhmUzQrdUROS1VFZHR6ZmVyaFcvdGJaRVc1eEdhMWJyajdlSSt1MkxtSmt4N3hpSjJaTk9rUmo2V01TQitMR29NcFdNanNSOStqUDg5dmYyU0hhMHFEYWxrdDRuYUFHY3BtZ2ttMk1mZWR2clhvZDg4NmlWT01USitZVGpBZVluWjR4d3lUbmNJUzhJM3RBdU14RG9ZZlhBNHp1QzNlY1o5aWJCTTh3YmpDZ09tUWdjbUtPcGk2TjZzWkdXNHg3Z3Yrd0RUcHliLzlJQkZwSm84cERVeE1xWG5mbXBrRVVpdEtVb3Z5RW5QaWkxS0xNeEtMVXVGT2syYjBuTmZJTEhaczdwdVRkKy8vazJSZ0VHZUFBbFVoYVVZM0taZzZNM2hncERLSU12RjRpVEhFQ0FreE1DU1pNakFBQUFjNWh2UT0=')

// Theoretically we should be able to get a RecordProxy that would allow us to navigate the links..
//   https://relay.dev/docs/api-reference/store/#recordproxy
//   TODO: figure out how to do that..

// But, we can always do it manually..
messageRef = __RELAY_DEVTOOLS_HOOK__.environments.get(1).getStore().getSource().get("UzpfSTM5MTMxMzg2NzY3MzE2NDpTY2FfSUZTY2ExY2EtMjE1MTUxMDY3NTUzMDEyMDA3N2NhZUp4RlUyMUlrd0VRdnJ0M2pUVFJHdk10bW83QXBaUi9wRkFTKzZHUUdtRmYrS2NjZndsaTZrU0F1Uk9wUDVFb2RZOG1ZTm14K3BqbHNUQnM1NWxwQmNjNG1zYWJicE1BbFpvTEUwNUUybTZSaGlIeWh6M3YwNTdwNDdIdTZlWXhkYjV6a3IxOTZjY2ZpYU5IVFFIM0w4ZFAwYVlWR0IzSGZQVGhEcGVKdVJ1Um5lV2t2TEMvY2NPZm1ycnlEbUNLaU91THZmMmZhS2loaEJjY0wrWE5QeDllN1JqZzg3TVhFT0RCNWJ3REJKdTJYclRuN1VaUTlaeWdBM2RId2NjK09PRWNjTTB1TEgxYnE5cGc3eTR4c1JubURQQWttRVZtSFBJem9KTkNPTk1vZnFZRjZlUWhsbXJRTTIwUFdyczlBb2ovRGtNbDRTNk5QYU9YMC80RTJMd2lzRWpoaGt4Qk1Rd0o0WjVNYmlGaVg4VklYU3c0R1RKeG9MN29vcm5rVnBlcnN4d3JnZkJjY2xyT0R1Rkc5bXF2Zm1CcStUM2ZWRWkyUW1vcDRQNXNmeHR0ZTh1MzhyVFQvZlpBOElKSi9FTDErSVNCMTRmbWgzR1N2aVlFWnpQQ3RCYkJwMFdZMFNJRXRBamJlb1NvSGlHaVIxaXpJM2hHRVliZklDdzNJcXkyWWlmVFBZb3c2RUN3T0JBTURnVHJaeno3ZWhiQk5vdlE0a0xvN1VIbzYwSGdoaEEwcnhCOEh4RVdzRDNCSTdua2xsQkFjY210ZXNvaldmRFNlcDBoS01PVnJOWG1UUStpNEdSQzBkYUNQSWxrLzA5MU03RVpjY0JwcVUwSmFKMFN4U0R5QUV4eEJXM1FqZEh4Qyt5MmlpTXJZTHN3ZEQzUHJLczU1SjJoNURpaXEyMjhOTFNWUDNlSWI3d1lXQWNjeXQ1cHlHR2JQSWpxZnhJV2orUzJvK2s5eVBwL0VqVFhpU0xpSXhIek14NGpvbUpKbFp3L0oxZEtmU1dxVTdwazZtN0xFYnBaSHlEYVh0U0VjY1R5ckk0bmpNbE94TDlzVUJISmE5V05sSFMxdUx5OHFMUlllcU9vdEhoUVE0ZXUxOStSMTQyclNTU3JxcXF1cjFiV3ltcWtkK3VVaXVvYXViU3lUaTdiVFprMHhMdW1ySlczTkJFcDZnNVVMQUtBNHhDejB4SVJVM0xBeHBhZ016clYra25ORU9OS29MejRzOGdoTlRIcENnc1ZBZ0hBN1Z5QS8vN2FlaTQ9OmFjYTFjYXtpY2ExMjIxMDE3MDk0ODAyODI2NDM7c2NhOTFjYSJBSUBBUUlnY2NhYjdlYmIzMVpkVzRzeXRSRVVqMUxaX1hYM2U0aV81d3p4NEtSNmNjZUZ4bFRYdmEwVF9JMGUzNUtvSlpSVV9ncDB3ay1BeHdzUk9KdHVVZVFVY2NEIjt9").message.__ref

__RELAY_DEVTOOLS_HOOK__.environments.get(1).getStore().getSource().get(messageRef)

Given a page such as:

This will attempt to extract the Story ID's of the posts on the page, then look them up in the Relay store to access the related data:

findAllReactElements(({ props, fiber, element }) => {
  return props?.children?.props?.hasOwnProperty('feedEdge')
}).map(el => {
  const props = getReactProps(el)
  const relayId = props?.children?.props?.feedEdge?.node?.__id
  const relayStore = __RELAY_DEVTOOLS_HOOK__.environments.get(1).getStore().getSource()
  const story = relayStore.get(relayId)
  const storyActors = story.actors.__refs.map(actorRef => {
    const actor = relayStore.get(actorRef)
    return {
      id: actor.id,
      last_active_time: actor.last_active_time,
      name: actor.name,
      url: actor.url,
      __actor: actor,
    }
  })
  const storyMessage = relayStore.get(story?.message?.__ref)
  const storyShareable = relayStore.get(story?.shareable?.__ref)
  return {
    actorName: storyActors?.[0]?.name,
    actors: storyActors,
    text: storyMessage?.text,
    shareable: storyShareable,
    sponsoredData: story.sponsored_data,
    creationTime: new Date(story.creation_time*1000).toString(),
    url: story.url,
    postId: story.post_id,
    storyId: story.__id,
    __story: story,
    __storyMessage: storyMessage,
    __storyShareable: storyShareable,
  }
}).filter(story => !story.sponsoredData)

News feed posts section:

// News Feed posts
$$('[aria-label="Feeds"] > div > div > div > div > div:nth-child(2) > div > div > div > h2')[0].innerText

// Post Container
postsContainer = $$('[aria-label="Feeds"] > div > div > div > div > div:nth-child(2) > div > div > div > h2 + div')
// News Feed posts
Array.from($$('[aria-label="Feeds"] h2')).map(h2 => h2.innerText)
Array.from($$('[aria-label="Feeds"] h2')).filter(h2 => h2.innerText === "News Feed posts")

// Post Container
postsContainer = Array.from($$('[aria-label="Feeds"] h2')).filter(h2 => h2.innerText === "News Feed posts")?.[0]?.nextSibling

postsContainer.children // 18
postsContainer.querySelectorAll('div > [aria-labelledby]') // 15

postsContainer.children[0].querySelectorAll('div > div > div > div > div > div > div > div > [aria-labelledby]')[0].attributes['aria-labelledby'].value
postsContainer.children[0].querySelectorAll('div > div > div > div > div > div > div > div > [aria-labelledby]')[0].attributes['aria-describedby'].value.split(' ').map(id => document.getElementById(id)).filter(Boolean)

postsContainer.children[0].querySelectorAll('div > [aria-labelledby]')[0].attributes['aria-labelledby'].value
postsContainer.children[0].querySelectorAll('div > [aria-labelledby]')[0].attributes['aria-describedby'].value.split(' ').map(id => document.getElementById(id)).filter(Boolean)

Array.from(postsContainer.children[1].querySelectorAll('[dir="auto"]')).map(el => el.innerText).filter(text => !['Facebook', 'Like', 'Comment', 'Share'].includes(text))

Feed Item Person/Page name (seemingly doesn't always match):

labelId = postsContainer.children[0].querySelectorAll('div > [aria-labelledby]')[0].attributes['aria-labelledby'].value
document.getElementById(labelId).querySelector('span > strong a').innerText
Array.from(postsContainer.querySelectorAll('div > [aria-labelledby]'))
  .map(el => el.attributes['aria-labelledby'].value)
  .filter(Boolean)
  .map(id => document.getElementById(id)?.innerText)

React Props/Fibre:

// Function to get find all elements connected to React
function findAllReactElements(customPredicate = () => true, rootNode = document, tagQuery = '*') {
  if (typeof customPredicate !== 'function') throw 'customPredicate must be a function returning a boolean'
  if (typeof rootNode?.['querySelector'] !== 'function') return [];

  const foundElements = rootNode.getElementsByTagName(tagQuery);
  return Array.from(foundElements).filter(element => {
    const keys = Object.getOwnPropertyNames(element);
    const reactPropsKey = keys.find(key => key.startsWith('__reactProps$'));
    const reactFiberKey = keys.find(key => key.startsWith('__reactFiber$'));
    const props = reactPropsKey ? element[reactPropsKey] : null;
    const fiber = reactFiberKey ? element[reactFiberKey] : null;

    return (props || fiber) && customPredicate({ props, fiber, element });
  });
}

// Function to get React props for a given HTML element
function getReactProps(element) {
  const propsKey = Object.getOwnPropertyNames(element).find(propName =>
    propName.startsWith('__reactProps$')
  );
  return propsKey ? element[propsKey] : null;
}

// Function to get React fiber for a given HTML element
function getReactFiber(element) {
  const fiberKey = Object.getOwnPropertyNames(element).find(propName =>
    propName.startsWith('__reactFiber$')
  );
  return fiberKey ? element[fiberKey] : null;
}
postsContainer = Array.from($$('[aria-label="Feeds"] h2')).filter(h2 => h2.innerText === "News Feed posts")?.[0]?.nextSibling

foundReactElements = findAllReactElements(({ props, fiber, element }) => {
  return props?.children?.props?.type === 'body3'
}, postsContainer.children[0])

foundReactElements.map(el => el.innerText)
postsContainer = Array.from($$('[aria-label="Feeds"] h2')).filter(h2 => h2.innerText === "News Feed posts")?.[0]?.nextSibling

foundReactElements = findAllReactElements(({ props, fiber, element }) => {
  return fiber?.return?.elementType?.displayName?.includes('BaseCometTextWithEntities.react')
}, document)

foundReactElements.map(el => el.innerText)
``

```js
postsContainer = Array.from($$('[aria-label="Feeds"] h2')).filter(h2 => h2.innerText === "News Feed posts")?.[0]?.nextSibling

foundReactElements = findAllReactElements(({ props, fiber, element }) => {
  return props?.children?.type?.displayName === 'TetraText'
}, postsContainer.children[0])

foundReactElements.map(el => el.innerText).filter(text => text !== 'Facebook')
getReactProps($0).children.map(child => child.type.displayName)

[
  "a [from CometFeedStoryCopyrightViolationHeaderSection.react]",
  "a [from CometFeedStoryHeaderSection.react]",
  "a [from CometFeedStoryContextSection.react]",
  "a [from CometFeedStoryPostInformTreatmentSection.react]",
  "a [from CometFeedStoryAYMTFooterSection.react]",
  "q [from CometPostInformTreatmentContext]",
  "a [from CometFeedStoryCallToActionSection.react]",
  "a [from CometFeedStoryFeedbackSection.react]",
  "a [from CometFeedStoryOuterFooterSection.react]"
]
postsContainer = Array.from($$('[aria-label="Feeds"] h2')).filter(h2 => h2.innerText === "News Feed posts")?.[0]?.nextSibling

foundReactElements = findAllReactElements(({ props, fiber, element }) => {
  const displayName = fiber?.return?.elementType?.displayName

  return [
    'BaseCometTextWithEntities.react',
    'CometTextWithEntitiesBase.react',
    'CometEmoji.react',
    'CometImage.react',
    'BaseImage.react',
  ].some(n => displayName?.includes(n))
}, document)

function renderReactElement(element) {
  const props = getReactProps(element) || {};

  const children = Array.isArray(props?.children) ? props.children : [props?.children]

  const { text, alt } = props
  const renderedSelf = (text || alt) ? { text, alt } : null

  return [
    renderedSelf,
    ...children.map(renderChild),
  ].flat().filter(Boolean)
}

function renderChild(child) {
  if (!child) return null;

  const props = child?.props || {}
  const displayName = child?.type?.displayName || child?.type?.render?.displayName
  const children = Array.isArray(props?.children) ? props.children : [props?.children]

  const { text, alt } = props
  const renderedSelf = (displayName || text || alt) ? { displayName, text, alt } : null

  return [
    renderedSelf,
    ...children.map(renderChild)
  ].flat().filter(Boolean)

  // if (child.type && child.props) {
  //   if (child.type.displayName?.includes('CometTextWithEntitiesBase.react') || 
  //       child.type.displayName?.includes('BaseCometTextWithEntities.react')) {
  //     return child.props.text;
  //   } 
  //   else if (child.type.render?.displayName?.includes('CometImage.react')) {
  //     return child.props.alt;
  //   } 
  //   else {
  //     // Recursive call to handle nested components or elements
  //     return renderReactElement(child);
  //   }
  // }
  // return null;
}

foundReactElements.map(element => {
  // const fiber = getReactFiber(element)
  // const props = getReactProps(element)

  return renderReactElement(element)
  // const children = Array.isArray(props?.children) ? props.children : [props?.children]

  // return children.map(child => {
  //   if (child?.type?.displayName?.includes('CometTextWithEntitiesBase.react') || child?.type?.displayName?.includes('BaseCometTextWithEntities.react')) {
  //     return child?.props?.text
  //   } 
  //   else if (child?.type?.render?.displayName.includes('CometImage.react')) {
  //     return child?.props?.alt
  //   } 
  //   else {
  //     return child
  //   }
  // })
}).flat()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment