Лаборатория веб-возможностей

Веб-возможности

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

Однако есть некоторые возможности, такие как доступ к файловой системе и обнаружение бездействия, которые доступны в нативном режиме, но недоступны в Интернете. Эти недостающие возможности означают, что некоторые типы приложений не могут быть доставлены в Интернет или менее полезны.

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

Что вы будете строить

В этой кодовой лаборатории вы поиграете с несколькими веб-API, которые являются совершенно новыми или доступны только под флагом. Таким образом, эта лаборатория кода фокусируется на самих API и на вариантах использования, которые эти API открывают, а не на создании конкретного конечного продукта.

Что вы узнаете

Эта лаборатория кода научит вас основам работы нескольких передовых API. Обратите внимание, что эта механика еще не высечена на камне, и мы очень ценим ваши отзывы о процессе разработки.

Что вам понадобится

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

Как подойти к кодлабу

Лаборатория кода не обязательно предназначена для последовательной обработки. Каждый раздел представляет собой независимый API, поэтому не стесняйтесь выбирать то, что вас больше всего интересует.

Цель Badging API — привлечь внимание пользователей к тому, что происходит в фоновом режиме. Для простоты демонстрации в этой лаборатории кода давайте воспользуемся API, чтобы привлечь внимание пользователей к чему-то, что происходит на переднем плане. Затем вы можете мысленно перенестись на то, что происходит на заднем плане.

Установить Airhorner

Чтобы этот API работал, вам нужно PWA, установленное на главном экране, поэтому первым шагом будет установка PWA, такого как печально известный, всемирно известный airhorner.com . Нажмите кнопку « Установить » в правом верхнем углу или используйте меню из трех точек для установки вручную.

Появится запрос подтверждения, нажмите « Установить ».

Теперь у вас есть новый значок в доке вашей операционной системы. Щелкните ее, чтобы запустить PWA. Он будет иметь собственное окно приложения и работать в автономном режиме.

Установка значка

Теперь, когда у вас установлено PWA, вам нужны некоторые числовые данные (значки могут содержать только числа) для отображения на значке. В The Air Horner легко подсчитать , сколько раз он был поднят рогами. На самом деле, с установленным приложением Airhorner попробуйте подать сигнал и проверить значок. Он считает один всякий раз, когда вы рожок.

Так как же это работает? По сути, код такой:

let hornCounter = 0;
const horn = document.querySelector('.horn');
horn.addEventListener('click', () => {
  navigator.setExperimentalAppBadge(++hornCounter);
});

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

Удаление значка

Счетчик доходит до 99, а затем начинается сначала. Вы также можете сбросить его вручную. Откройте вкладку консоли DevTools, вставьте строку ниже и нажмите Enter.

navigator.setExperimentalAppBadge(0);

Кроме того, вы также можете избавиться от значка, явно очистив его, как показано в следующем фрагменте. Теперь значок вашего PWA должен снова выглядеть, как в начале, четким и без значка.

navigator.clearExperimentalAppBadge();

Обратная связь

Что вы думаете об этом API? Пожалуйста, помогите нам, кратко ответив на этот опрос:

Был ли этот API интуитивно понятным?

Да Нет

Удалось запустить пример?

Да Нет

Есть что сказать? Были ли недостающие функции? Пожалуйста , дайте быстрый отзыв в этом опросе . Благодарю вас!

Native File System API позволяет разработчикам создавать мощные веб-приложения, взаимодействующие с файлами на локальном устройстве пользователя. После того как пользователь предоставит веб-приложению доступ, этот API позволяет веб-приложениям считывать или сохранять изменения непосредственно в файлах и папках на устройстве пользователя.

Чтение файла

«Привет, мир» API-интерфейса Native File System заключается в чтении локального файла и получении содержимого файла. Создайте простой файл .txt и введите текст. Затем перейдите на любой безопасный сайт (т. е. сайт, обслуживаемый через HTTPS), например example.com , и откройте консоль DevTools . Вставьте приведенный ниже фрагмент кода в консоль. Поскольку Native File System API требует жеста пользователя, мы прикрепляем к документу обработчик двойного щелчка. Нам понадобится дескриптор файла позже, поэтому мы просто сделаем его глобальной переменной.

document.ondblclick = async () => {
  window.handle = await window.chooseFileSystemEntries();
  const file = await handle.getFile();
  document.body.textContent = await file.text();
};

Если затем дважды щелкнуть в любом месте страницы example.com , появится средство выбора файлов.

Выберите файл .txt , который вы создали ранее. Содержимое файла затем заменит фактическое содержимое body example.com .

Сохранение файла

Далее мы хотим внести некоторые изменения. Поэтому давайте сделаем body редактируемым, вставив фрагмент кода ниже. Теперь вы можете редактировать текст, как если бы браузер был текстовым редактором.

document.body.contentEditable = true;

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

document.onclick = async () => {
  const writer = await handle.createWriter();
  await writer.truncate(0);
  await writer.write(0, document.body.textContent);
  await writer.close();
};

Теперь, когда вы щелкаете (не дважды щелкаете) документ, появляется запрос на разрешение. Когда вы даете разрешение, содержимое файла будет тем, что вы редактировали в body ранее. Проверьте изменения, открыв файл в другом редакторе (или запустите процесс снова, дважды щелкнув документ и повторно открыв файл).

Поздравляем! Вы только что создали самый маленький текстовый редактор в [citation needed] .

Обратная связь

Что вы думаете об этом API? Пожалуйста, помогите нам, кратко ответив на этот опрос:

Был ли этот API интуитивно понятным?

Да Нет

Удалось запустить пример?

Да Нет

Есть что сказать? Были ли недостающие функции? Пожалуйста , дайте быстрый отзыв в этом опросе . Благодарю вас!

API обнаружения формы обеспечивает доступ к ускоренным детекторам формы (например, для человеческих лиц) и работает с неподвижными изображениями и/или потоками живых изображений. Операционные системы имеют эффективные и оптимизированные детекторы функций, такие как Android FaceDetector . API обнаружения форм открывает эти нативные реализации и предоставляет их через набор интерфейсов JavaScript.

В настоящее время поддерживаются следующие функции: обнаружение лиц через интерфейс FaceDetector , обнаружение штрих-кода через интерфейс BarcodeDetector и обнаружение текста (оптическое распознавание символов) через интерфейс TextDetector .

Распознавание лиц

Увлекательная функция API обнаружения формы — распознавание лиц. Для проверки нам нужна страница с лицами. Эта страница с лицом автора — хорошее начало. Это будет выглядеть примерно так, как на скриншоте ниже. В поддерживаемом браузере будут распознаны граница лица и ориентиры лица.

Вы можете увидеть, как мало кода потребовалось для этого, перемикшировав или отредактировав проект Glitch , особенно файл script.js .

Если вы хотите работать полностью динамично, а не просто работать с лицом автора, перейдите на эту страницу результатов поиска Google, полную лиц, в приватной вкладке или в гостевом режиме. Теперь на этой странице откройте Инструменты разработчика Chrome, щелкнув правой кнопкой мыши в любом месте и выбрав Inspect . Затем на вкладке «Консоль» вставьте приведенный ниже фрагмент. Код будет выделять обнаруженные лица полупрозрачной красной рамкой.

document.querySelectorAll('img[alt]:not([alt=""])').forEach(async (img) => {
  try {
    const faces = await new FaceDetector().detect(img);
    faces.forEach(face => {
      const div = document.createElement('div');
      const box = face.boundingBox;
      const computedStyle = getComputedStyle(img);
      const [top, right, bottom, left] = [
        computedStyle.marginTop,
        computedStyle.marginRight,
        computedStyle.marginBottom,
        computedStyle.marginLeft
      ].map(m => parseInt(m, 10));
      const scaleX = img.width / img.naturalWidth;
      const scaleY = img.height / img.naturalHeight;
      div.style.backgroundColor = 'rgba(255, 0, 0, 0.5)';
      div.style.position = 'absolute';
      div.style.top = `${scaleY * box.top + top}px`;
      div.style.left = `${scaleX * box.left + left}px`;
      div.style.width = `${scaleX * box.width}px`;
      div.style.height = `${scaleY * box.height}px`;
      img.before(div);
    });
  } catch(e) {
    console.error(e);
  }
});

Вы заметите, что есть некоторые сообщения DOMException , и не все изображения обрабатываются. Это связано с тем, что изображения в верхней части страницы встроены как URI данных и, таким образом, к ним можно получить доступ, тогда как изображения в нижней части страницы поступают из другого домена, не настроенного для поддержки CORS . Ради демонстрации нам не нужно беспокоиться об этом.

Обнаружение ориентиров лица

Помимо самих лиц, macOS также поддерживает определение ориентиров лица. Чтобы протестировать обнаружение ориентиров лица, вставьте следующий фрагмент в консоль. Напоминание: набор ориентиров совсем не идеален из-за crbug.com/914348 , но вы можете видеть, к чему все идет и насколько мощной может быть эта функция.

document.querySelectorAll('img[alt]:not([alt=""])').forEach(async (img) => {
  try {
    const faces = await new FaceDetector().detect(img);
    faces.forEach(face => {
      const div = document.createElement('div');
      const box = face.boundingBox;
      const computedStyle = getComputedStyle(img);
      const [top, right, bottom, left] = [
        computedStyle.marginTop,
        computedStyle.marginRight,
        computedStyle.marginBottom,
        computedStyle.marginLeft
      ].map(m => parseInt(m, 10));
      const scaleX = img.width / img.naturalWidth;
      const scaleY = img.height / img.naturalHeight;
      div.style.backgroundColor = 'rgba(255, 0, 0, 0.5)';
      div.style.position = 'absolute';
      div.style.top = `${scaleY * box.top + top}px`;
      div.style.left = `${scaleX * box.left + left}px`;
      div.style.width = `${scaleX * box.width}px`;
      div.style.height = `${scaleY * box.height}px`;
      img.before(div);

      const landmarkSVG = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
      landmarkSVG.style.position = 'absolute';
      landmarkSVG.classList.add('landmarks');
      landmarkSVG.setAttribute('viewBox', `0 0 ${img.width} ${img.height}`);
      landmarkSVG.style.width = `${img.width}px`;
      landmarkSVG.style.height = `${img.height}px`;
      face.landmarks.map((landmark) => {                    
        landmarkSVG.innerHTML += `<polygon class="landmark-${landmark.type}" points="${
        landmark.locations.map((point) => {          
          return `${scaleX * point.x},${scaleY * point.y} `;
        }).join(' ')
      }" /></svg>`;          
      });
      div.before(landmarkSVG);
    });
  } catch(e) {
    console.error(e);
  }
});

Обнаружение штрих-кода

Вторая функция API обнаружения формы — обнаружение штрих-кода. Как и раньше, нам нужна страница со штрих-кодами, такая как эта . Когда вы откроете его в браузере, вы увидите различные расшифрованные QR-коды. Сделайте ремикс или отредактируйте проект Glitch , особенно файл script.js, чтобы посмотреть, как это делается.

Если вы хотите что-то более динамичное, мы снова можем использовать Google Image Search. На этот раз в браузере перейдите на эту страницу результатов поиска Google в приватной вкладке или в гостевом режиме. Теперь вставьте приведенный ниже фрагмент на вкладку Chrome DevTools Console. Через некоторое время распознанные штрих-коды будут снабжены аннотациями с необработанным значением и типом штрих-кода.

document.querySelectorAll('img[alt]:not([alt=""])').forEach(async (img) => {
  try {
    const barcodes = await new BarcodeDetector().detect(img);
    barcodes.forEach(barcode => {
      const div = document.createElement('div');
      const box = barcode.boundingBox;
      const computedStyle = getComputedStyle(img);
      const [top, right, bottom, left] = [
        computedStyle.marginTop,
        computedStyle.marginRight,
        computedStyle.marginBottom,
        computedStyle.marginLeft
      ].map(m => parseInt(m, 10));
      const scaleX = img.width / img.naturalWidth;
      const scaleY = img.height / img.naturalHeight;
      div.style.backgroundColor = 'rgba(255, 255, 255, 0.75)';
      div.style.position = 'absolute';
      div.style.top = `${scaleY * box.top + top}px`;
      div.style.left = `${scaleX * box.left - left}px`;
      div.style.width = `${scaleX * box.width}px`;
      div.style.height = `${scaleY * box.height}px`;
      div.style.color = 'black';
      div.style.fontSize = '14px';      
      div.textContent = `${barcode.rawValue}`;
      img.before(div);
    });
  } catch(e) {
    console.error(e);
  }
});

Обнаружение текста

Последней функцией API обнаружения формы является обнаружение текста. Теперь вы знаете, как это сделать: нам нужна страница с изображениями, содержащими текст, как эта с результатами сканирования Google Книг. В поддерживаемых браузерах вы увидите распознанный текст и ограничивающую рамку, нарисованную вокруг фрагментов текста. Сделайте ремикс или отредактируйте проект Glitch , особенно файл script.js, чтобы посмотреть, как это делается.

Для динамического тестирования перейдите на эту страницу результатов поиска в приватной вкладке или в гостевом режиме. Теперь вставьте приведенный ниже фрагмент на вкладку Chrome DevTools Console. Немного подождав, часть текста будет распознана.

document.querySelectorAll('img[alt]:not([alt=""])').forEach(async (img) => {
  try {
    const texts = await new TextDetector().detect(img);
    texts.forEach(text => {
      const div = document.createElement('div');
      const box = text.boundingBox;
      const computedStyle = getComputedStyle(img);
      const [top, right, bottom, left] = [
        computedStyle.marginTop,
        computedStyle.marginRight,
        computedStyle.marginBottom,
        computedStyle.marginLeft
      ].map(m => parseInt(m, 10));
      const scaleX = img.width / img.naturalWidth;
      const scaleY = img.height / img.naturalHeight;
      div.style.backgroundColor = 'rgba(255, 255, 255, 0.75)';
      div.style.position = 'absolute';
      div.style.top = `${scaleY * box.top + top}px`;
      div.style.left = `${scaleX * box.left - left}px`;
      div.style.width = `${scaleX * box.width}px`;
      div.style.height = `${scaleY * box.height}px`;
      div.style.color = 'black';
      div.style.fontSize = '14px';      
      div.innerHTML = text.rawValue;
      img.before(div);
    });
  } catch(e) {
    console.error(e);
  }
});

Обратная связь

Что вы думаете об этом API? Пожалуйста, помогите нам, кратко ответив на этот опрос:

Был ли этот API интуитивно понятным?

Да Нет

Удалось запустить пример?

Да Нет

Есть что сказать? Были ли недостающие функции? Пожалуйста , дайте быстрый отзыв в этом опросе . Благодарю вас!

API Web Share Target позволяет установленным веб-приложениям регистрироваться в базовой операционной системе в качестве цели общего доступа для получения общего содержимого либо из API Web Share, либо из системных событий, таких как кнопка общего доступа на уровне операционной системы.

Установите PWA, чтобы поделиться с ним

В качестве первого шага вам понадобится PWA, которым вы можете поделиться. На этот раз Airhorner (к счастью) не справится с этой задачей, но демонстрационное приложение Web Share Target прикроет вашу спину. Установите приложение на главный экран вашего устройства.

Поделитесь чем-нибудь с PWA

Затем вам нужно чем-то поделиться, например, фотографией из Google Фото. Используйте кнопку «Поделиться» и выберите Scrapbook PWA в качестве цели общего доступа.

Когда вы коснетесь значка приложения, вы попадете прямо в PWA Scrapbook, и фотография будет прямо там.

Так как же это работает? Чтобы узнать это, изучите манифест веб-приложения Scrapbook PWA. Конфигурация для работы целевого API веб-ресурса находится в "share_target" манифеста, которое в поле "action" указывает на URL-адрес, который получает параметры, указанные в "params" .

Затем сторона, предоставляющая общий доступ, заполняет этот шаблон URL соответствующим образом (либо с помощью действия общего доступа, либо программно управляется разработчиком с помощью API Web Share ), чтобы принимающая сторона могла затем извлечь параметры и что-то с ними сделать, например отобразить. .

{
  "action": "/_share-target",
  "enctype": "multipart/form-data",
  "method": "POST",
  "params": {
    "files": [{
      "name": "media",
      "accept": ["audio/*", "image/*", "video/*"]
    }]
  }
}

Обратная связь

Что вы думаете об этом API? Пожалуйста, помогите нам, кратко ответив на этот опрос:

Был ли этот API интуитивно понятным?

Да Нет

Удалось запустить пример?

Да Нет

Есть что сказать? Были ли недостающие функции? Пожалуйста , дайте быстрый отзыв в этом опросе . Благодарю вас!

Чтобы избежать разрядки аккумулятора, большинство устройств быстро переходят в спящий режим, если их не использовать. Хотя в большинстве случаев это нормально, некоторым приложениям необходимо держать экран или устройство в активном состоянии, чтобы завершить свою работу. API-интерфейс Wake Lock позволяет предотвратить затемнение и блокировку экрана устройства, а также предотвратить переход устройства в спящий режим. Эта возможность открывает новые возможности, для которых раньше требовалось собственное приложение.

Настроить заставку

Чтобы протестировать API Wake Lock, вы должны сначала убедиться, что ваше устройство переходит в спящий режим. Поэтому на панели настроек вашей операционной системы активируйте заставку по вашему выбору и убедитесь, что она запускается через 1 минуту. Убедитесь, что он работает, оставив свое устройство в покое ровно на это время (да, я знаю, это больно). На приведенных ниже снимках экрана показана macOS, но вы, конечно, можете попробовать это на своем мобильном устройстве Android или любой поддерживаемой настольной платформе.

Установить блокировку пробуждения экрана

Теперь, когда вы знаете, что ваш скринсейвер работает, вы будете использовать блокировку пробуждения типа "screen" , чтобы помешать скринсейверу выполнять свою работу. Перейдите к демонстрационному приложению Wake Lock и установите флажок « Активировать screen Wake Lock» .

С этого момента активируется wake lock. Если вы достаточно терпеливы, чтобы не трогать свое устройство в течение минуты, вы увидите, что заставка действительно не запустилась.

Так как же это работает? Чтобы узнать это, перейдите к проекту Glitch для демо-приложения Wake Lock и проверьте script.js . Суть кода в фрагменте ниже. Откройте новую вкладку (или используйте любую открытую вкладку) и вставьте приведенный ниже код в консоль инструментов разработчика Chrome. Когда вы щелкаете по окну, вы должны увидеть блокировку пробуждения, которая активна ровно 10 секунд (см. журналы консоли), и ваша заставка не должна запускаться.

if ('wakeLock' in navigator && 'request' in navigator.wakeLock) {  
  let wakeLock = null;
  
  const requestWakeLock = async () => {
    try {
      wakeLock = await navigator.wakeLock.request('screen');
      wakeLock.addEventListener('release', () => {        
        console.log('Wake Lock was released');                    
      });
      console.log('Wake Lock is active');      
    } catch (e) {      
      console.error(`${e.name}, ${e.message}`);
    } 
  };

  requestWakeLock();
  window.setTimeout(() => {
    wakeLock.release();
  }, 10 * 1000);
}

Обратная связь

Что вы думаете об этом API? Пожалуйста, помогите нам, кратко ответив на этот опрос:

Был ли этот API интуитивно понятным?

Да Нет

Удалось запустить пример?

Да Нет

Есть что сказать? Были ли недостающие функции? Пожалуйста , дайте быстрый отзыв в этом опросе . Благодарю вас!

API, который нам очень нравится, — это API выбора контактов. Это позволяет веб-приложению получать доступ к контактам из собственного диспетчера контактов устройства, поэтому ваше веб-приложение имеет доступ к именам, адресам электронной почты и номерам телефонов ваших контактов. Вы можете указать, хотите ли вы только один или несколько контактов и хотите ли вы все поля или только подмножество имен, адресов электронной почты и телефонных номеров.

Соображения конфиденциальности

Когда окно выбора откроется, вы сможете выбрать контакты, которыми хотите поделиться. Вы заметите, что нет опции «выбрать все», что является преднамеренным: мы хотим, чтобы совместное использование было сознательным решением. Точно так же доступ не является непрерывным, а является разовым решением.

Доступ к контактам

Доступ к контактам является простой задачей. Перед тем, как откроется окно выбора, вы можете указать, какие поля вам нужны ( name , email и telephone ), и хотите ли вы получить доступ к нескольким контактам или только к одному. Вы можете протестировать этот API на Android-устройстве, открыв демонстрационное приложение . Соответствующий раздел исходного кода , по сути, представляет собой фрагмент ниже:

getContactsButton.addEventListener('click', async () => {
  const contacts = await navigator.contacts.select(
      ['name', 'email'],
      {multiple: true});
  if (!contacts.length) {
    // No contacts were selected, or picker couldn't be opened.
    return;
  }
  console.log(contacts);
});

Копирование и вставка текста

До сих пор не было возможности программно копировать и вставлять изображения в системный буфер обмена. Недавно мы добавили поддержку изображений в API асинхронного буфера обмена,

так что теперь вы можете копировать и вставлять изображения. Что нового, так это то, что вы также можете записывать изображения в буфер обмена. API асинхронного буфера обмена уже некоторое время поддерживает копирование и вставку текста. Вы можете скопировать текст в буфер обмена, вызвав navigator.clipboard.writeText(), а затем вставить этот текст, вызвав navigator.clipboard.readText().

Копирование и вставка изображений

Теперь вы также можете записывать изображения в буфер обмена. Чтобы это работало, вам нужны данные изображения в виде большого двоичного объекта, который вы затем передаете конструктору элемента буфера обмена. Наконец, вы можете скопировать этот элемент буфера обмена, вызвав navigator.clipboard.write().

// Copy: Writing image to the clipboard
try {
  const imgURL = 'https://developers.google.com/web/updates/images/generic/file.png';
  const data = await fetch(imgURL);
  const blob = await data.blob();
  await navigator.clipboard.write([
    new ClipboardItem(Object.defineProperty({}, blob.type, {
      value: blob,
      enumerable: true
    }))
  ]);
  console.log('Image copied.');
} catch(e) {
  console.error(e, e.message);
}

Вставка изображения обратно из буфера обмена выглядит довольно сложной, но на самом деле это всего лишь возврат большого двоичного объекта из элемента буфера обмена. Поскольку их может быть несколько, вам нужно перебирать их, пока не будет найден тот, который вас интересует. Из соображений безопасности сейчас это ограничено изображениями PNG, но в будущем может поддерживаться больше форматов изображений.

async function getClipboardContents() {
  try {
    const clipboardItems = await navigator.clipboard.read();
    for (const clipboardItem of clipboardItems) {
      try {
        for (const type of clipboardItem.types) {
          const blob = await clipboardItem.getType(type);
          console.log(URL.createObjectURL(blob));
        }
      } catch (e) {
        console.error(e, e.message);
      }
    }
  } catch (e) {
    console.error(e, e.message);
  }
}

Вы можете увидеть этот API в действии в демонстрационном приложении , соответствующие фрагменты из его исходного кода встроены выше. Копирование изображений в буфер обмена может осуществляться без разрешения, но вам необходимо предоставить доступ для вставки из буфера обмена.

После предоставления доступа вы можете прочитать изображение из буфера обмена и вставить его в приложение:

Поздравляем, вы добрались до конца кодлаба. Опять же, это приятное напоминание о том, что большинство API-интерфейсов все еще находятся в процессе разработки и над ними ведется активная работа. Поэтому команда очень ценит ваши отзывы , так как только взаимодействие с такими людьми, как вы , поможет нам сделать эти API правильными.

Мы также рекомендуем вам почаще заглядывать на нашу целевую страницу «Возможности» . Мы будем поддерживать его в актуальном состоянии, и в нем есть ссылки на все подробные статьи по API, над которыми мы работаем. Продолжай качаться!

Том и вся команда возможностей 🐡