Сервисный работник отвечает на выборку после получения данных от другого работника
Я использую сервисных работников для перехвата запросов и предоставления ответов на запросы выборки, общаясь с веб-работником (также созданным с той же родительской страницы). Я использовал каналы сообщений для прямой связи между работником и служащим. Вот простой POC, который я написал:
var otherPort, parentPort;
var dummyObj;
var DummyHandler = function()
{
this.onmessage = null;
var selfRef = this;
this.callHandler = function(arg)
{
if (typeof selfRef.onmessage === "function")
{
selfRef.onmessage(arg);
}
else
{
console.error("Message Handler not set");
}
};
};
function msgFromW(evt)
{
console.log(evt.data);
dummyObj.callHandler(evt);
}
self.addEventListener("message", function(evt) {
var data = evt.data;
if(data.msg === "connect")
{
otherPort = evt.ports[1];
otherPort.onmessage = msgFromW;
parentPort = evt.ports[0];
parentPort.postMessage({"msg": "connect"});
}
});
self.addEventListener("fetch", function(event)
{
var url = event.request.url;
var urlObj = new URL(url);
if(!isToBeIntercepted(url))
{
return fetch(event.request);
}
url = decodeURI(url);
var key = processURL(url).toLowerCase();
console.log("Fetch For: " + key);
event.respondWith(new Promise(function(resolve, reject){
dummyObj = new DummyHandler();
dummyObj.onmessage = function(e)
{
if(e.data.error)
{
reject(e.data.error);
}
else
{
var content = e.data.data;
var blob = new Blob([content]);
resolve(new Response(blob));
}
};
otherPort.postMessage({"msg": "content", param: key});
}));
});
Роли портов:
otherPort: общение с работником
parentPort: связь с родительской страницей
У работника, у меня есть база данных, это сказать:
var dataBase = {
"file1.txt": "This is File1",
"file2.txt": "This is File2"
};
Работник просто передает правильные данные в соответствии с ключом, отправленным работником службы. На самом деле это будут очень большие файлы.
Проблема, с которой я сталкиваюсь, заключается в следующем:
- Поскольку я использую глобальный dummyObj, старый dummyObj и, следовательно, более старый onmessage теряются, и только последний ресурс отвечает полученными данными.
- На самом деле, file2 получает
This is File1
потому что последний dummyObj предназначен для file2.txt, но рабочий сначала отправляет данные для file1.txt.
Я попытался создать iframe напрямую, и все запросы внутри него перехватываются:
<html>
<head></head>
<body><iframe src="tointercept/file1.txt" ></iframe><iframe src="tointercept/file2.txt"></iframe>
</body>
</html>
Вот что я получаю в качестве вывода:
Одним из подходов может быть запись всех файлов, которые могут быть извлечены в IndexedDB на рабочем месте перед созданием iframe. Затем в Service Worker извлеките их из проиндексированной БД. Но я не хочу сохранять все ресурсы в IDB. Так что такой подход не то, что я хочу.
Кто-нибудь знает способ выполнить то, что я пытаюсь сделать каким-либо другим способом? Или есть исправление того, что я делаю.
Пожалуйста помоги!
ОБНОВИТЬ
Я заставил это работать, помещая dummyObjs в глобальную очередь вместо того, чтобы иметь глобальный объект. И при получении ответа от работника в msgFromW
Я выталкиваю элемент из очереди и вызываю его callHandler
функция. Но я не уверен, что это надежное решение. Как предполагается, что все будет происходить по порядку. Это предположение верно?
1 ответ
Я бы порекомендовал обернуть ваше сообщение, передаваемое между работником службы и веб-работником, в обещания, а затем передать обещание, которое сопоставляется с данными веб-работника, в fetchEvent.respondWith()
,
promise-worker
библиотека может автоматизировать эту упаковку обещаний для вас, или вы можете сделать это вручную, используя этот пример в качестве руководства.
Если бы вы использовали promise-worker
, ваш код будет выглядеть примерно так:
var promiseWorker = new PromiseWorker(/* your web worker */);
self.addEventListener('fetch', function(fetchEvent) {
if (/* some optional check to see if you want to handle this event */) {
fetchEvent.respondWith(promiseWorker.postMessage(/* file name */));
}
});