Постоянный сервис-воркер в расширении Chrome

Мне нужно определить моего Service Worker как постоянного в моем расширении Chrome, потому что я использую API webRequest для перехвата некоторых данных, переданных в форме для определенного запроса, но я не знаю, как это сделать. Я все перепробовал, но мой Service Worker продолжает выгружаться.

Как мне держать его загруженным и ждать, пока запрос не будет перехвачен?

8 ответов

В вашем случае это, вероятно, ошибка в ManifestV3, https://crbug.com/1024211: рабочий не просыпается для событий webRequest.

Поэтому просто используйте ManifestV2, пока это не будет исправлено, потому что не существует такой вещи, как постоянный сервис-воркер.

Если вы все еще хотите предотвратить выгрузку фонового сервис-воркера, вам придется открыть порт обмена сообщениями из любого сценария содержимого вкладки.или с другой страницы расширения, например всплывающей страницы. Этот порт будет работать в течение пяти минут (это ошибка / функция в ManifestV3), поэтому вам придется использовать событие порта onDisconnect, чтобы снова подключиться к какой-либо случайной вкладке. Обратной стороной этого обходного пути является то, что только веб-страницы запускают сценарии содержимого, поэтому, если в браузере нет вкладок с веб-страницами, вам не повезло. Еще одним недостатком является необходимость в сценариях содержимого для всех URL-адресов, что является широким разрешением хоста, которое помещает большинство расширений в очередь медленных проверок в интернет-магазине. Теоретически расширение может быть даже отклонено в интернет-магазине, если команда Chromium продолжит игнорировать установленный факт, что иногда необходимо иметь постоянный фоновый скрипт в течение произвольного времени, но будем надеяться, что они 'Я предоставлю API для управления этим поведением без необходимости прибегать к таким грязным хитростям.

в отличие от chrome.webRequest API , chrome.webNavigation API работает отлично, потому что chrome.webNavigation API может разбудить сервисного работника , а пока вы можете попробовать поместить chrome.webRequest API в chrome.webNavigation .

      chrome.webNavigation.onBeforeNavigate.addListener(function(){

   chrome.webRequest.onResponseStarted.addListener(function(details){

      //.............
      
      //.............

   },{urls: ["*://domain/*"],types: ["main_frame"]});


},{
    url: [{hostContains:"domain"}]
});

Если я правильно понимаю, вы можете разбудить сервисного работника (background.js) с помощью предупреждений. Посмотрите на пример ниже:

  1. манифест v3
      "permissions": [
    "alarms"
],
  1. сервисный работник background.js:
      chrome.alarms.create({ periodInMinutes: 4.9 })
chrome.alarms.onAlarm.addListener(() => {
  console.log('log for debug')
});

К сожалению, это не моя проблема, и у вас может быть другая проблема. Когда я обновляю расширение dev или останавливаю и запускаю расширение prod, какое-то время сервисный работник вообще умирает. Когда я закрываю и открываю рабочий браузер, он не запускается, и любые слушатели внутри рабочего тоже не запускаются. Он попытался зарегистрировать воркер вручную. Например:

      // override.html
<!DOCTYPE html>
<html lang="en">

  <head>...<head>
  <body>
    ...
    <script defer src="override.js"></script>
  <body>
<html>
      // override.js - this code is running in new tab page
navigator.serviceWorker.getRegistrations().then((res) => {
  for (let worker of res) {
    console.log(worker)
    if (worker.active.scriptURL.includes('background.js')) {
      return
    }
  }

  navigator.serviceWorker
    .register(chrome.runtime.getURL('background.js'))
    .then((registration) => {
      console.log('Service worker success:', registration)
    }).catch((error) => {
      console.log('Error service:', error)
    })
})

Это решение частично помогло мне, но это не имеет значения, потому что мне нужно зарегистрировать воркер на разных вкладках. Может кто знает решение. Я буду рад.

Я нашел другое решение для сохранения расширения. Он улучшает ответ wOxxOm, используя дополнительное расширение, чтобы открыть порт подключения к нашему основному расширению. Затем оба расширения пытаются связаться друг с другом в случае разрыва соединения, тем самым сохраняя работоспособность обоих.

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

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

Как ответил Clairzil Bawon samdi, что chrome.webNavigation может разбудить работника службы в MV3, вот обходной путь в моем случае:

      // manifest.json
...
"background": {
  "service_worker": "background.js"
},
"host_permissions": ["https://example.com/api/*"],
"permissions": ["webRequest", "webNavigation"]
...

В моем случае он прослушивает событие onHistoryStateUpdated , чтобы разбудить работника службы:

      // background.js
chrome.webNavigation.onHistoryStateUpdated.addListener((details) => {
  console.log('wake me up');
});

chrome.webRequest.onSendHeaders.addListener(
  (details) => {
    // code here
  },
  {
    urls: ['https://example.com/api/*'],
    types: ['xmlhttprequest'],
  },
  ['requestHeaders']
);

ИМХО (и непосредственный опыт) хорошо структурированное ПО будет работать вечно.

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

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

Из «Искусства войны» (Сунь-цзы): если не можешь с этим бороться, подружись с ним.

так что... хорошо, давайте попробуем время от времени дать что-то последовательное для размышлений нашему ПО и поставить "заплатку" (потому что это ЗАПЛАТА!) к этой проблеме.

Очевидно, я не уверяю, что это решение будет работать для всех вас, но оно работало для меня в прошлом, прежде чем я решил просмотреть всю логику и код моего ПО.

Поэтому я решил поделиться им для ваших собственных тестов.

  • Для этого не требуется никаких специальных разрешений в манифесте V3.
  • Не забудьте вызвать функцию StayAlive() ниже при запуске ПО.
  • Для выполнения надежных тестов не забудьте не открывать страницы DevTools. Вместо этого используйте chrome://serviceworker-internals и найдите журнал (Scope) вашего идентификатора расширения.

РЕДАКТИРОВАТЬ:

Так как логика кода может быть кому-то непонятна, постараюсь объяснить, чтобы развеять сомнения:

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

Приведенный ниже код подключается к именованному порту и пытается отправить через него сообщение несуществующему слушателю (поэтому он будет генерировать ошибки).

При этом ПО активно и работает (ему есть чем заняться, т. е. отправить сообщение через порт).

Поскольку никто не слушает, он генерирует (пойманную и зарегистрированную) ошибку (в onDisconnect) и завершает работу (нормальное поведение, происходящее в любом коде).

Но через 25 секунд он делает то же самое с самого начала, таким образом сохраняя ПО активным навсегда.

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

          // Forcing service worker to stay alive by sending a "ping" to a port where noone is listening
    // Essentially it prevents SW to fall asleep after the first 30 secs of work.
    
    const INTERNAL_STAYALIVE_PORT = "Whatever_Port_Name_You_Want"
    var alivePort = null;
    ...
    StayAlive();
    ...
    
    async function StayAlive() {
    var lastCall = Date.now();
    var wakeup = setInterval( () => {
        
        const now = Date.now();
        const age = now - lastCall;            
        
        console.log(`(DEBUG StayAlive) ----------------------- time elapsed: ${age}`)
        if (alivePort == null) {
            alivePort = chrome.runtime.connect({name:INTERNAL_STAYALIVE_PORT})

            alivePort.onDisconnect.addListener( (p) => {
                if (chrome.runtime.lastError){
                    console.log(`(DEBUG StayAlive) Disconnected due to an error: ${chrome.runtime.lastError.message}`);
                } else {
                    console.log(`(DEBUG StayAlive): port disconnected`);
                }

                alivePort = null;
            });
        }

        if (alivePort) {
                        
            alivePort.postMessage({content: "ping"});
            
            if (chrome.runtime.lastError) {                              
                console.log(`(DEBUG StayAlive): postMessage error: ${chrome.runtime.lastError.message}`)                
            } else {                               
                console.log(`(DEBUG StayAlive): "ping" sent through ${alivePort.name} port`)
            }
            
        }         
        //lastCall = Date.now();             
        
    }, 25000);
}

Надеясь, что это поможет кому-то.

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

РЕДАКТИРОВАТЬ (17 января 2023 г.)

когда вы думаете, что достигли дна, следите за люком, который может внезапно открыться под вашими ногами. Сунь Цзы

Эта версия функции StayAlive() выше по-прежнему сохраняет активным сервис-воркер, но избегает вызова функции каждые 25 секунд, чтобы не обременять ее ненужной работой.

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

Как это работает

Первый вызов Highlander() выполняется до истечения судьбоносных 30 секунд (здесь он выполняется через 4 секунды после старта сервис-воркера).

Последующие вызовы выполняются до истечения судьбоносных 5 минут (здесь они выполняются каждые 270 секунд).

Сервисный работник, таким образом, никогда не заснет и всегда будет реагировать на все события.

Таким образом, получается, что в дизайне Chromium после первого вызова Highlander() в течение первых 30 секунд внутренняя логика, управляющая жизнью сервис-воркера (MV3), продлевает период полной активности до следующих 5 минут.

Это действительно очень весело...

в любом случае... это ServiceWorker.js, который я использовал для своих тестов.

      // -----------------
// SERVICEWORKER.JS
// -----------------

const INTERNAL_STAYALIVE_PORT = "CT_Internal_port_alive"
var alivePort = null;

const SECONDS = 1000;
var lastCall = Date.now();
var isFirstStart = true;
var timer = 4*SECONDS;
// -------------------------------------------------------
var wakeup = setInterval(Highlander, timer);
// -------------------------------------------------------
    
async function Highlander() {

    const now = Date.now();
    const age = now - lastCall;
    
    console.log(`(DEBUG Highlander) ------------- time elapsed from first start: ${convertNoDate(age)}`)
    if (alivePort == null) {
        alivePort = chrome.runtime.connect({name:INTERNAL_STAYALIVE_PORT})

        alivePort.onDisconnect.addListener( (p) => {
            if (chrome.runtime.lastError){
                console.log(`(DEBUG Highlander) Expected disconnect (on error). SW should be still running.`);
            } else {
                console.log(`(DEBUG Highlander): port disconnected`);
            }

            alivePort = null;
        });
    }

    if (alivePort) {
                    
        alivePort.postMessage({content: "ping"});
        
        if (chrome.runtime.lastError) {                              
            console.log(`(DEBUG Highlander): postMessage error: ${chrome.runtime.lastError.message}`)                
        } else {                               
            console.log(`(DEBUG Highlander): "ping" sent through ${alivePort.name} port`)
        }            
    }         
    //lastCall = Date.now();
    if (isFirstStart) {
        isFirstStart = false;
        clearInterval(wakeup);
        timer = 270*SECONDS;
        wakeup = setInterval(Highlander, timer);
    }        
}

function convertNoDate(long) {
    var dt = new Date(long).toISOString()
    return dt.slice(-13, -5) // HH:MM:SS only
}

РЕДАКТИРОВАТЬ (20 января 2023 г.):

На Github я создал репозиторий для практического примера того, как правильно использовать функцию Highlander в реальном расширении. При реализации этого репо я также учел комментарии wOxxOm к моему посту (большое ему спасибо).

По-прежнему на Github я создал еще один репозиторий , чтобы продемонстрировать в другом расширении реального мира, как сервис-воркер может немедленно запуститься сам по себе (перевести себя в статус RUNNING), без помощи внешних скриптов контента, и как он может жить вечно, используя обычный Функция горца. Этот репозиторий включает в себя локальный сервер WebSocket Echo Test, который используется расширением в образце связи с клиентом и полезен для внешней отладки расширения, когда браузер хоста расширения закрыт . Это верно, потому что, в зависимости от типа применяемой конфигурации, при закрытии хост-браузера Highlander-DNA может либо закрыться вместе с браузером, либо продолжать жить вечно., со всеми подключенными и управляемыми функциями (например, включенный тестовый образец связи клиент/сервер WebSocket).

РЕДАКТИРОВАТЬ (22 января 2023 г.)

Я проверил потребление памяти и ЦП, когда Service Worker всегда находится в состоянии RUNNING из-за использования Highlander. Потребление, чтобы он работал все время, практически НУЛЕВОЕ. Я действительно не понимаю, почему команда Chromium настаивает на том, чтобы излишне усложнять всем жизнь.

      setInterval(()=>{self.serviceWorker.postMessage('test')},20000)

Понятия не имею, как это работает, но, похоже, сервисный работник не уснет в Chrome 100, 105, 108 и 114. На других версиях не проверялся.

WebSocket обратные вызовы, зарегистрированные из chrome.runtimeрегистрации слушателей сервис-воркера моих расширений не вызывались, что похоже на почти ту же проблему.

Я подошел к этой проблеме, убедившись, что мой сервис-воркер никогда не заканчивается, добавив к нему следующий код:

      function keepServiceRunning() {
    setTimeout(keepServiceRunning, 2000);
  }

keepServiceRunning()

После этого мои обратные вызовы теперь вызываются, как и ожидалось.

Другие вопросы по тегам