Во время моего последнего клиентского проекта у нас было требование двунаправленной связи между расширением браузера и собственным приложением, работающим на компьютере пользователя.
Первая идея, которая пришла в голову, заключалась в создании сервера веб-сокетов, который работает только локально. и отвечает на запросы расширения.
Потом оказалось, что это невозможное решение из-за соображений безопасности заказчика.
Нуждаясь в другом решении, мы наткнулись на термин «родной обмен сообщениями». Поэтому эта статья о моих приключениях с ним и о том, как мы реализовали его для Microsoft Edge в Windows.

Примечание. Части реализации различаются в зависимости от операционной системы и/или браузера, поэтому ознакомьтесь также с соответствующей документацией. Я постараюсь отметить части, которые отличаются.

Так что же такое «родной обмен сообщениями»?

Собственный обмен сообщениями — это механизм связи между веб-приложением, поддерживаемый во всех современных браузерах (Firefox, Chrome, Edge) для обмена сообщениями JSON в кодировке UTF8 между расширением браузера и собственным хост-приложением.

Вы можете задаться вопросом: Зачем мне это делать?

Ну, потому что исполняемый файл хоста работает за пределами песочницы браузера и поэтому способен на то, что расширение просто не может сделать. Вещи как:

  • показывает собственный пользовательский интерфейс
  • подключение к сети
  • чтение и запись файлов
  • вызов API операционной системы

В нашем случае мы хотели реализовать функцию возврата и оформления заказа, а также требовали вызова и автоматизации некоторых внешних приложений, таких как Microsoft Outlook, через COM-взаимодействие.
Поскольку обычные веб-страницы не могут взаимодействовать напрямую с зарегистрированными узлами Native Messaging, они должны использовать API передачи сообщений, чтобы вместо этого дать указание расширению взаимодействовать с ним. Это ограничение добавляет сложности, но также повышает безопасность.

Процесс регистрации для хоста Native Messaging

Чтобы зарегистрировать новый хост Native Messaging, нам сначала нужен файл манифеста JSON. Типичный манифест может выглядеть так:

{
  "name": "com.company.product",
  "description": "product description",
  "path": "absolute executable path",
  "type": "stdio",
  "allowed_origins": [
     "chrome-extension://extension_id/", 
     "chrome-extension://extension_id/"
  ]
}

Как видите, манифест содержит свойство «путь»¹, которое содержит путь к исполняемому файлу, который необходимо запустить.
Еще одно важное свойство — «allowed_origins»², которое содержит список расширений, которые могут вызывать этот хост.

Примечание¹: В Windows можно использовать относительный путь. В OS X и Linux можно использовать только абсолютный путь.
Примечание². Макет манифеста отличается в Chrome и Firefox. Пожалуйста, ознакомьтесь с соответствующей документацией.

Следующим шагом является создание раздела реестра под \Software\Microsoft\Edge\NativeMessagingHosts\ со значением по умолчанию, которое указывает на ранее созданный файл манифеста. Вы можете создать его в улье HKCU или HKLM. Для ключа в кусте HKLM требуются права администратора, и он обычно используется в корпоративных сценариях (политика запрета узлов Native Messaging, определяемых на уровне пользователя). При определении ключа в кусте HKCU он имеет приоритет над ключом, определенным в HKLM.

Чтобы добавить ключ в куст HKCU, запустите сеанс cmd и выполните следующую команду после того, как вы настроили его для своих нужд:

REG ADD "HKCU\Software\Google\Chrome\NativeMessagingHosts\com.company.product" /ve /t REG_SZ /d "path_to_manifest_file" /f

После этого и перезапуска браузера процесс «регистрации» завершен.

Собственный хост обмена сообщениями

Итак, теперь давайте начнем с того, как будет выглядеть рабочий процесс при запуске хоста Native Messaging. Пожалуйста, взгляните на схему:

Как вы можете видеть на диаграмме, расширение — это та часть, которая инициирует процесс запуска нашего хоста. Либо с помощью вызова chrome.runtime.connectNative, либо chrome.runtime.sendNativeMessage.
Разница между этими двумя вызовами заключается в том, что connectNative используется, когда нам нужно постоянное соединение, потому что нам нужно обмениваться несколькими сообщениями.
С другой стороны, sendNativeMessage можно использовать для одноразовых запросов. Оба метода имеют параметр для включения уникального extensionId, чтобы сообщить браузеру, какой хост мы хотим запустить. Затем браузер проверяет, что расширение имеет разрешение nativeMessaging в своем манифесте и что хост с уникальным extensionId зарегистрирован.
Если оба случая верны, браузер порождает хост как отдельный процесс и передает ему два аргумента командной строки (расширение источника, дескриптор браузеру).
Канал связи между расширением и хостом теперь установлен и может использоваться для обмена сообщениями.

Следующая важная часть головоломки Native Messaging — понять, как работает протокол и какие у нас есть ограничения.
С исходной точки зрения сообщения отправляются через стандартный ввод (stdin) и принимаются через стандартный вывод (stdout).

В нашем приложении сообщение, инициирующее процесс оформления заказа, выглядит так:

{
    "id": "unique request/response id",
    "command": "checkout",
    "transfer": {
      "id": "unique file id",
      "filename": "filename.extension",
      "chunk": "part of the file content as base64",
      "status": "sending"
    }
}

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

{
    "id": "unique request/response id",
    "status": "success"
}

Или в случае такой ошибки:

{
    "id": "unique request/response id",
    "status": "error",
    "errors": [
      "unique_error_key",
      "unique_error_key"
    ]
}

Как вы можете видеть выше, мы отправляем разбиения файлов. Это связано с тем, что максимальный размер сообщения для хоста ограничен 1 МБ. Расширение способно отправлять сообщения размером до 4 ГБ. Поскольку эта концепция не привязана к конкретной платформе, вы можете реализовать хосты на всех языках программирования и во всех операционных системах, поддерживающих стандартный ввод и стандартный вывод. Вы можете найти несколько примеров для него на GitHub.

Расширение собственного обмена сообщениями

Каждое упакованное расширение браузера содержит файл манифеста с различными свойствами, описывающими расширение, такими как имя, версия, описание, разрешения и т. д. Чтобы иметь возможность вызывать хост Native Messaging, расширение нуждается в разрешении «nativeMessaging»:

{
   "name":"extension name",
   "version":"0.0.0.1",
   "manifest_version":2,
   "description":"extension description",
   "icons":{
      "32":"icons/logo-32x32.png",
      "64":"icons/logo-64x64.png",
      "128":"icons/logo-128x128.png"
   },
   "background":{
      "scripts":[
         "js/background.js"
      ]
   },
   "permissions":[
      "nativeMessaging"
   ]
}

Как уже указывалось ранее, запуск хоста запускается вызовом chrome.runtime.connectNative или chrome.runtime.sendNativeMessage. Вызов chrome.runtime.connectNative возвращает объект порта, который предлагает некоторые события, на которые вы можете подписаться, а также метод postMessage для отправки сообщения на другой конец сети. трубка.

Соображения безопасности

Поскольку Native Messaging работает только в пределах компьютера, риск безопасности минимален, если хост был тщательно реализован.
Злоумышленник должен быть уже на машине, чтобы иметь возможность злоупотреблять ею.

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

Надеюсь, вам понравился мой вступительный пост о Native Messaging, который также был моим первым постом на medium.com 😊!