JS расширение в Chrome. При вызове нативного приложения с активной вкладки - в консоли отображается ошибка

JS расширение в Chrome.
При вызове нативного приложения с активной вкладки - в консоли отображается ошибка.

Uncaught TypeError: chrome.runtime.sendNativeMessage is not a function
    at background.js:7:20

Я просто ручной тестировщик.
У нас есть скрипт на python (.exe) для получения сборок приложений.
При запуске из cmd в первом аргументе скрипт ожидает ID тестовой задачи.
Но каждый раз запускать его из cmd вручную не хочется.

Есть идея по кнопке получать ID задачи с активной страницы в браузере и передавать его в скрипт для получения сборок.

  1. Было создано маленькое JS расширение для браузера Chrome.
  2. А так же маленькое нативное приложение на python (.exe).
    Которое принимает сообщение от расширения и в будущем будет вызывать скрипт для получения сборок - оно написано согласно dev.chrome ресурсу и работает корректно.

Если вызывать chrome.runtime.sendNativeMessage из отладки страниц chrome://extensions/ моего приложения - сообщение нативным приложением принимается и ошибки нет.

Упрощённый пример кода (оставил только вызов chrome.runtime.sendNativeMessage)

Манифест расширения

{
    "name": "sample: GetDevApk",
    "description": "Get apk from YouTrack",
    "version": "1.0",
    "manifest_version": 3,
    "action": {
        "default_popup": "popup.html"
    },
    "permissions": [
        "scripting",
        "activeTab",
        "nativeMessaging"
    ],
    "background": {
        "service_worker": "background.js",
        "type": "module"
    },
    "content_scripts": [
        {
            "matches": ["https://youtrack.sample.ru/issue/*"],
            "js": ["background.js"]
        }
    ],
    "author": "User3000"
}

background.js

let id_task = 'Example-111';
const app_name = 'ru.sample.n_app';
const message = {get_apk: id_task};

setTimeout(() => {

    chrome.runtime.sendNativeMessage(
        app_name,
        message,
        function (response) {
            console.log('Response:', response);
        },
    );

},  3500);

popup.js

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    chrome.runtime.sendNativeMessage(
        app_name,
        message,
        function(response) {
            sendResponse(response);
        }
    );

    return true;
});

Манифест нативного приложения (на всякий случай)

{
  "name": "ru.sample.n_app",
  "description": "n_app for get_apk",
  "path": "n_app.exe",
  "type": "stdio",
  "allowed_origins": [
    "chrome-extension://loanjfflomlmjmmdjmcfmepeajaffpkk/"
  ]
}

Объявление app_name и message в popup.js результата не изменяет.
Буду благодарен за любые подсказки.
Заранее спасибо!


Ответы (1 шт):

Автор решения: Артём Дюков

Удалось разобраться!
Нужно было больше времени уделить на изучение первоисточника "chrome.runtime", а не точечно искать информацию на различных ресурсах.
Плюс помог конкретный пример "Send data from a content script to the service worke" из того же первоисточника.
Так же, как итог, очень сильно помог разбор кода рабочего расширения от Microsoft: Single Sing On.

Если коротко, как я понял (может и не правильно), то:

  • background.js не имеет доступа к dom по умолчанию, но используется как сервис для прослушивания и обработки событий (service_worker)
    -> он может взаимодействовать с нативными приложениями через sendNativeMessage и другими прописанными расширениями, выполнять некоторые действия
  • content.js (у меня был popen.js) для обработки контента на активной станице
    -> он не может вызвать нативное приложение через sendNativeMessage - будет ошибка is not a function
    -> но он может послать сообщение sendMessage, которое сможет прослушать background.js используя onMessage.addListener
  • popup.js нужен для обработки действий выполняемых внутри формы расширения (когда кликаем на него и открывается собственное меню или мини страничка popup.html)
    -> он может получить содержимое активной страницы и внести изменения, если это необходимо, но для этого нужно будет дописать небольшой обработчик
    -> и событие будет происходить по нажатию на кнопку в расширении (которую нужно добавить, конечно), а не сразу по открытию вкладки

Так или иначе, все эти названия файлов - достаточно условны, вся соль в особенностях работы и тут уже решает то что именно указано в манифесте и документации.
С JS раньше не работал - по сути, первый проект, по этому простите - если задел чьи-то чувства! Готов к конструктивной критике и советам.

Всем спасибо!
Вот упрощённый рабочий пример с комментариями по коду.

манифест расширения

{
    "name": "sample: GetDevApk",
    "description": "Get apk from YouTrack",
    "version": "1.0",
    "manifest_version": 3,
    "permissions": [
        "scripting",
        "activeTab",
        "nativeMessaging"
    ],
    "background": {
        "service_worker": "background.js"
    },
    "content_scripts": [
        {
            "all_frames": true,
            "matches": ["https://youtrack.sample.ru/issue/*"],
            "js": ["content.js"]
        } ]
}

background.js

const app_name = 'ru.sample.n_app';


chrome.runtime.onMessage.addListener(
    function (request, sender, sendResponse){
            // request тут - это наше сообщение (id задачи)
            // формируем сообщение для нативного приложения в формате json
            let state = {get_apk: request};
            // вызываем наше нативное приложение 
            // тут нет аргумента function(response) т.к. обработка ошибок пока не требуется
            chrome.runtime.sendNativeMessage(app_name, state);
            // отправляем ответ в content.js
            sendResponse({get_apk: "Execute", status: "Success", id_task: request});
            return true  // говорим, что всё "ОК"
    });

content.js

// задержка перед выполнением
setTimeout(() => {
    // формируем сообщение для background.js
    // слушатель со стороны background.js должен его получить
    // и выполнить необходимое нам действие sendNativeMessage
    chrome.runtime.sendMessage(
        'Example-111',  // наше сообщение (id задачи)
        (response) => {  // обработка ответа
            console.log(response)  // вывод ответа в консоль
            return true  // говорим, что всё "ОК"
        }
    );
},  2500);
→ Ссылка