Hello everyone,
I am trying to implement offline functionality for the Odoo Sales app using a custom service worker. I am facing an issue where the
service worker fails to fetch the list of assets to cache during its install event.
Here's what I have done so far:
- Created a custom Odoo module (pwa_sales_offline) that depends on web, sale_management, and stock.
- Created a custom service worker at pwa_sales_offline/static/src/service_worker.js.
- Overridden the default service worker by creating a controller that inherits from odoo.addons.web.controllers.webmanifest.WebManifest and overrides the _get_service_worker_content method to serve my custom file.
- Created a controller to get the list of assets. This controller calls request.env['ir.qweb']._get_asset_nodes('web.assets_backend', True, True, False, False, request.env.context) and returns the list of asset URLs as a JSON response.
- The service worker's install event fetches this list of assets and tries to cache them using cache.addAll().
The Problem:
The fetch call within the service worker's install event fails with the error: Uncaught (in promise) TypeError: Failed to fetch.
What I have tried:
* I have verified that the controller that returns the list of assets is working correctly by opening its URL (/pwa_sales_offline/assets)
directly in the browser. It successfully returns a JSON array of asset URLs.
* I have tried changing the auth of the controller to public.
* I have tried changing the type of the controller to both http and json.
* I have added a check in the service worker's fetch event to prevent it from intercepting its own requests to the assets endpoint.
The Code:
Here is the relevant code:
import logging
import json
from odoo import http
from odoo.http import request
from odoo.tools import file_open
from odoo.addons.web.controllers.webmanifest import WebManifest
_logger = logging.getLogger(__name__)
class PwaSalesOfflineWebManifest(WebManifest):
def _get_service_worker_content(self):
_logger.info("Serving custom service worker")
with file_open('pwa_sales_offline/static/src/service_worker.js') as f:
return f.read()
@http.route('/pwa_sales_offline/assets', type='http', auth='public', methods=['GET'], csrf=False)
def get_assets(self):
_logger.info("Getting assets for PWA")
qweb = request.env['ir.qweb']
nodes = qweb._get_asset_nodes('web.assets_backend', True, True, False, False, request.env.context)
files = []
for node in nodes:
if len(node) == 2:
tag, attrs = node
if 'href' in attrs:
files.append(attrs['href'])
elif 'src' in attrs:
files.append(attrs['src'])
elif len(node) == 3:
tag, attrs, content = node
if 'href' in attrs:
files.append(attrs['href'])
elif 'src' in attrs:
files.append(attrs['src'])
_logger.info("Found %d assets", len(files))
return request.make_json_response(files)
const STATIC_CACHE_NAME = 'odoo-sales-offline-static-cache';
self.addEventListener('install', (event) => {
console.log('Custom Service Worker: Installed!');
event.waitUntil(
fetch('/pwa_sales_offline/assets').then((response) => {
return response.json();
}).then((files) => {
return caches.open(STATIC_CACHE_NAME).then((cache) => {
return cache.addAll(files);
});
})
);
});
self.addEventListener('fetch', (event) => {
console.log('Custom Service Worker: Fetching', event.request.url);
const url = new URL(event.request.url);
if (url.pathname === '/pwa_sales_offline/assets') {
return;
}
// Serve static assets from cache
if (url.origin === location.origin) {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
return;
}
// Cache data
const DATA_CACHE_NAME = 'odoo-sales-offline-data-cache';
if (url.pathname === '/web/dataset/search_read') {
event.respondWith(
fetch(event.request.clone()).then((response) => {
if (response.status === 200) {
event.request.clone().json().then((payload) => {
if (payload.params && (payload.params.model === 'res.partner' || payload.params.model === 'product.product')) {
const cacheRequest = event.request.clone();
const cacheResponse = response.clone();
caches.open(DATA_CACHE_NAME).then((cache) => {
cache.put(cacheRequest, cacheResponse);
});
}
});
}
return response;
}).catch(() => {
return caches.match(event.request);
})
);
}
});
I would appreciate any help or suggestions on how to solve this issue. Is there a better way to get the asset list from within the
service worker? Or is there something I'm missing about how fetch works in this context?
Thank you