Передача информации о состоянии в сервис-работника перед "установкой"
Фон
Я новичок в обслуживании, но работаю над библиотекой, которая должна стать "оффлайн-первой" (фактически, почти "автономной") (FWIW, цель состоит в том, чтобы позволить пользователям библиотеки предоставлять конфигурацию JSON, представляющую табличные многолинейные тексты и получить взамен приложение, которое позволяет своим пользователям просматривать эти тексты с высокой степенью настраиваемости по диапазонам абзацев/стихов).
Другими проектами являются установка библиотеки в качестве зависимости, а затем предоставление информации через наш JavaScript API, такой как путь к файлу конфигурации JSON, указывающий файлы, которые наше приложение будет использовать для создания (офлайн-приложения) для них.
Хотя я знаю, что мы могли бы сделать одно из следующего:
- требуют, чтобы пользователи предоставили жестко закодированный путь, из которого наш
install
скрипт нашего рабочего может использовать waitUntil
со своим собственным запросом JSON для извлечения необходимых файлов пользователя - пропустите шаг
install
рабочего сотрудника сервисного работника для файла JSON и полагайтесь на события fetch
чтобы обновить кеш, предоставив резервный дисплей, если пользователь завершил установку и отключился до того, как могут произойти выборки. - Отправьте некоторую информацию о состоянии из нашего основного сценария на сервер, на который работник службы, после регистрации, запросит запрос перед завершением своего события
install
.
... но все варианты кажутся менее идеальными, потому что, соответственно:
- Наши пользователи библиотеки могут предпочесть, чтобы они могли определять свое местоположение для своей конфигурации JSON.
- Учитывая, что конфигурация JSON определяет файлы, критически важные для того, чтобы показать своим пользователям что-нибудь полезное, я бы предпочел не допускать установку только для того, чтобы сказать, что пользователь должен вернуться в онлайн, чтобы получить остальную часть файлов, если они не смогли остаться онлайн после события
install
чтобы увидеть все необходимые выборки. - Кроме того, чтобы избежать большего количества поездок на сервер и дополнительного кода, я бы предпочел, чтобы наш код был настолько автономным, чтобы полностью работать на простых статических файловых серверах.
Вопрос:
Есть ли способ передать сообщение или информацию о состоянии в сервис-работник до того, как произойдет событие install
, будь то в строке запроса URL-адреса рабочего агента или через событие обмена сообщениями? Событие обмена сообщениями может даже технически прибыть после того, как событие install
начнется до тех пор, пока оно может произойти до того, как waitUntil
завершена install
waitUntil
.
Я знаю, что я мог бы проверить это сам, но я хотел бы знать, какие могут быть лучшие методы, когда критические файлы приложений должны быть динамически получены, как в таких библиотеках, как наши.
Я предполагаю, что indexedDB
может быть единственной альтернативой здесь (т. indexedDB
конфигурационную информацию или путь конфигурации JSON к индексированномуDB, зарегистрировать сервисного работника и получить данные индексированных данных из события install
)? Даже это не было бы идеальным, так как я позволяю пользователям определять пространство имен для их хранения, но мне нужен способ, чтобы он тоже мог быть передан работнику, иначе может возникнуть столкновение нескольких таких приложений в происхождении.
Ответы
Ответ 1
Использование параметра запроса
Если вы сочтете это полезным, то да, вы можете указать состояние во время установки сервисного работника, включив параметр запроса своему сервисному работнику при его регистрации, например:
// Inside your main page:
const pathToJson = '/path/to/file.json';
const swUrl = '/sw.js?pathToJson=' + encodeURIComponent(pathToJson);
navigator.serviceWorker.register(swUrl);
// Inside your sw.js:
self.addEventListener('install', event => {
const pathToJson = new URL(location).searchParams.get('pathToJson');
event.waitUntil(
fetch(pathToJson)
.then(response => response.json())
.then(jsonData => /* Do something with jsonData */)
);
});
Несколько замечаний по поводу этого подхода:
Если вы fetch()
JSON файл в своем обработчике install
(как в примере кода), это будет эффективно происходить один раз для каждой версии вашего сценария работника службы (sw.js
). Если содержимое файла JSON изменяется, но все остальное остается прежним, работник сервиса не обнаружит это автоматически и не заполнит ваши кеши.
Исходя из первого пункта, если вы обойдете это, например, включив управление версиями на основе хеш-функции в URL-адресе вашего файла JSON, каждый раз, когда вы изменяете этот URL-адрес, вы в конечном итоге устанавливаете нового работника сервиса. Это само по себе неплохо, но вы должны помнить об этом, если в вашем веб-приложении есть логика, которая прослушивает события жизненного цикла работника сервиса.
Альтернативные подходы
Вам также может оказаться проще просто добавлять файлы в кэш из контекста главной страницы, поскольку браузеры, поддерживающие Cache Storage API, предоставляют его через window.caches
. Преимущество предварительного кэширования файлов в обработчике install
сервисного работника заключается в том, что он гарантирует, что все файлы были успешно кэшированы до установки сервисного работника.
Другой подход заключается в том, чтобы записать информацию о состоянии в IndexedDB из контекста window
, а затем прочитать из IndexedDB внутри вашего обработчика сервисного работника install
.
Ответ 2
Обновление 3:
И поскольку небезопасно полагаться на глобальные переменные внутри рабочего, мое решение для обмена сообщениями выглядит еще менее звуковым. Я думаю, что это должно быть решение Джеффа Послика (в некоторых случаях importScripts
могут работать).
Обновление 2:
Хотя это не связано напрямую с темой этой темы, связанной с событием "установить", в соответствии с обсуждением, начинающимся с https://github.com/w3c/ServiceWorker/issues/659#issuecomment-384919053, есть некоторые проблемы, особенно с используя этот метод передачи сообщений для activate
события. А именно, событие activate
никогда не может потерпеть неудачу и, таким образом, никогда не будет проверено снова, оставив одно приложение в неустойчивом состоянии. (Неисправность install
по крайней мере не применит нового сервисного работника к старым страницам, в то время как activate
будет удерживать выборки до тех пор, пока событие не завершится, что никогда не будет выполнено, если оно осталось в ожидании сообщения, которое не было получено, и которое ничего, кроме нового работника, не будет исправлено, поскольку новые страницы не смогут загрузить, чтобы отправить это сообщение снова.)
Обновить:
Хотя я получил клиент из install
скрипта в Chrome, я почему-то не смог получить сообщение с помощью navigator.serviceWorker.onmessage
.
Однако я смог полностью подтвердить следующий подход:
В службе:
self.addEventListener('install', e => {
e.waitUntil(
new Promise((resolve, reject) => {
self.addEventListener('message', ({data: {
myData
}}) => {
// Do something with 'myData' here
// then when ready, 'resolve'
});
})
);
});
В вызывающем скрипте:
navigator.serviceWorker.register('sw.js').then((r) => {
r.installing.postMessage({myData: 100});
});
@JeffPosnick - лучший ответ для простого случая, который я описал в OP, но я подумал, что я расскажу о том, что можно получить сообщения от раннего (рабочего стола) сервисного работника (в Chrome), например, следующим образом:
В службе:
self.addEventListener('install', e => {
e.waitUntil(self.clients.matchAll({
includeUncontrolled: true,
type: 'window'
}).then((clients) => new Promise((resolve, reject) => {
if (clients && clients.length) {
const client = clients.pop();
client.postMessage('send msg to main script');
// One should presumably be able to poll to check for a
// variable set in the SW message listener below
// and then 'resolve' when set
// Despite the unreliability of setting globals in SW's
// I believe this could be safe here as the 'install'
// event is to run while the main script is still open.
}
})));
});
self.addEventListener('message', e => {
console.log('SW receiving main script msg', e.data);
e.ports[0].postMessage('sw response');
});
В вызывающем скрипте:
navigator.serviceWorker.addEventListener('message', (e) => {
console.log('msg recd in main script', e.data);
e.source.postMessage('sending back to sw');
});
return navigator.serviceWorker.register(
'sw.js'
).then((r) => {
// navigator.serviceWorker.ready.then((r) => { // This had been necessary at some point in my testing (with r.active.postMessage), but not working for me atm...
// Sending a subsequent message
const messageChannel = new MessageChannel();
messageChannel.port1.onmessage = (e) => {
if (e.data.error) {
console.log('err', e.data.error);
} else {
console.log('data', e.data);
}
};
navigator.serviceWorker.controller.postMessage('sending to sw', [messageChannel.port2]);
// });
});