Open Closed

offline capability with Blazor PWA #7510


User avatar
0
jayesh@extranerds.com created
  • ABP Framework version: v8.0.1
  • UI Type: Blazor WASM
  • Database System: EF Core (SQL Server)

Hi,

I am developing a Blazor Wasm application with offline capabilities. When application switch to offline mode, I want to handle the API calls like fetch openid-configuration, application-configuration, and call the identity server etc .currently i am using below service-worker code but am getting 403 error after login. Have you ever encountered this issue, or is there any alternative solution for calling the Api?

self.importScripts('./service-worker-assets.js');
self.addEventListener('install', event => event.waitUntil(onInstall(event)));
self.addEventListener('activate', event => event.waitUntil(onActivate(event)));
self.addEventListener('fetch', event => event.respondWith(onFetch(event)));

const cacheNamePrefix = 'offline-cache-';
const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
const offlineAssetsInclude = [/\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/];
const offlineAssetsExclude = [/^service-worker\.js$/];

async function onInstall(event) {
    console.info('Service worker: Install');
    self.skipWaiting();
    // Fetch and cache all matching items from the assets manifest
    const assetsRequests = self.assetsManifest.assets
        .filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url)))
        .filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url)))
        .map(asset => createRequest(asset));
    await caches.open(cacheName).then(cache => cache.addAll(assetsRequests));
}
function createRequest(asset) {
    try {
        return new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' });
    } catch (error) {
        console.error(`Error creating request for ${asset.url} with integrity ${asset.hash}:`, error);
        return new Request(asset.url, { cache: 'no-cache' });
    }
}
async function onActivate(event) {
    console.info('Service worker: Activate');
    // Delete unused caches
    const cacheKeys = await caches.keys();
    await Promise.all(cacheKeys
        .filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName)
        .map(key => caches.delete(key)));
  }

async function onFetch(event) {
    let cachedResponse = null;

    if (event.request.method === 'GET') {

        if (event.request.mode === 'navigate' &&
            (event.request.url.includes('/connect/') ||
                event.request.url.includes('/authentication/') ||             
                event.request.url.includes('well-known/') ||           
                event.request.url.includes('/api/abp/'))) {

            const cache = await caches.open(cacheName);
            cachedResponse = await cache.match(event.request);
            if (cachedResponse) {
                return cachedResponse;
            }
            else {
                const networkResponse = await fetch(event.request);
                if (networkResponse.ok) {
                    cache.put(event.request, networkResponse.clone());
                }
                return networkResponse;
            }
        }   
        const shouldServeIndexHtml = event.request.mode === 'navigate';

        const request = shouldServeIndexHtml ? 'index.html' : event.request;
        const cache = await caches.open(cacheName);
        cachedResponse = await cache.match(request);
    }
    return cachedResponse || fetch(event.request).catch(error => console.log(`error on fetch ${error}`));
}

11 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    We don't have any experience with this case. : (

  • User Avatar
    0
    jayesh@extranerds.com created

    can you please confirm that do we need to do any changes in service worker, or its fine ?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    I will ask our colleagues.

  • User Avatar
    0
    jayesh@extranerds.com created

    When i am running my application in offline mode its giving me the following error.

    Can you please suggest how I can handle this request in offline mode in blazor webassemby PWA

  • User Avatar
    0
    jayesh@extranerds.com created

    I am also getting 403 error

  • User Avatar
    0
    jayesh@extranerds.com created

    When i am running my application in offline mode its giving me the following error.

    Can you please suggest how I can handle this request in offline mode in blazor webassemby PWA

    can you please update me on this ?

  • User Avatar
    0
    jayesh@extranerds.com created

    I am also getting 403 error

    this issue is resolved.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Do you have any unresolved problems?

  • User Avatar
    0
    jayesh@extranerds.com created

    Hi, Could you please verify if the service worker code below is fine?

    self.importScripts('./service-worker-assets.js');
    self.addEventListener('install', event => event.waitUntil(onInstall(event)));
    self.addEventListener('activate', event => event.waitUntil(onActivate(event)));
    self.addEventListener('fetch', event => event.respondWith(onFetch(event)));
    
    const cacheNamePrefix = 'offline-cache-';
    const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
    const offlineAssetsInclude = [/\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/];
    const offlineAssetsExclude = [/^service-worker\.js$/];
    
    // Replace with your base path if you are hosting on a subfolder. Ensure there is a trailing '/'.
    const base = "/";
    const baseUrl = new URL(base, self.origin);
    const manifestUrlList = self.assetsManifest.assets.map(asset => new URL(asset.url, baseUrl).href);
    async function onInstall(event) {
        console.info('Service worker: Install');
        await onActivate(event);
        self.skipWaiting();
        // Fetch and cache all matching items from the assets manifest
        const assetsRequests = self.assetsManifest.assets
            .filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url)))
            .filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url)))
            .map(asset => createRequest(asset));
        assetsRequests.push(new Request('https://cdn.quilljs.com/1.3.7/quill.snow.css'));
        assetsRequests.push(new Request('https://cdn.quilljs.com/1.3.7/quill.js'));
    
        await caches.open(cacheName).then(cache => cache.addAll(assetsRequests)).catch(error => console.log(`error on add to cache ${error}`));
    }
    function createRequest(asset) {
        try {
            return new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' });
    
        } catch (error) {
            console.error(`Error creating request for ${asset.url} with integrity ${asset.hash}:`, error);
            return new Request(asset.url, { cache: 'no-cache' });
        }
    }
    async function onActivate(event) {
        console.info('Service worker: Activate');
    
        // Delete unused caches
        const cacheKeys = await caches.keys();
        await Promise.all(cacheKeys
            .filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName)
            .map(key => caches.delete(key)));
    }
    async function onFetch(event) {
        let cachedResponse = null;
    
        if (event.request.method === 'GET') {
    
            if (((event.request.mode === 'cors') && (event.request.url.includes('/api/abp/application-') ||
                event.request.url.includes('well-known/') ||
                event.request.url.includes('hotreload') ||
                event.request.url.includes('api-definition'))) || (event.request.mode === 'no-cors' && event.request.url.includes('profile-picture-file'))) {
                const cache = await caches.open(cacheName);
                if (!navigator.onLine) {
                    cachedResponse = await cache.match(event.request);
                    if (!cachedResponse && (event.request.url.includes('?v=') || event.request.url.includes('?_v='))) {
                        cachedResponse = await cache.match(event.request.url.replace(/\?v=.*$/, '').replace(/\?_v=.*$/, ''));
                    }
                }
                if (cachedResponse) {
                    return cachedResponse;
                }
                else {
                    const networkResponse = await fetch(event.request).catch(error => console.log(`error on fetch ${event.request.url} ${error}`));
                    if (networkResponse.ok) {
                        cache.put(event.request, networkResponse.clone());
                    }
                    if (event.request.url.includes('profile-picture-file')) {
                        cache.put(event.request, networkResponse.clone());
                    }
                    return networkResponse;
                }
            }
            // For all navigation requests, try to serve index.html from cache
            // If you need some URLs to be server-rendered, edit the following check to exclude those URLs
            const shouldServeIndexHtml = event.request.mode === 'navigate'
                && !event.request.url.includes('/Identity/')
                && !event.request.url.includes('/connect/')
                && !event.request.url.includes('/authentication/');
    
            const request = shouldServeIndexHtml ? 'index.html' : event.request;
    
            const cache = await caches.open(cacheName);
            cachedResponse = await cache.match(request);
            if (!cachedResponse && (event.request.url.includes('?v=') || event.request.url.includes('?_v='))) {
                cachedResponse = await cache.match(event.request.url.replace(/\?v=.*$/, '').replace(/\?_v=.*$/, ''));
            }
    
        }
        return cachedResponse || fetch(event.request).catch(error => console.log(`error on fetch ${event.request.url}  ${error}`));
    }
    
  • User Avatar
    0
    jayesh@extranerds.com created

    I have resolved most of the issues, but putting some conditions in service worker.js file as that was not there in default file that was provided by ABP solution, can you please confirm the code that I have written so far, thanks

  • User Avatar
    0
    EngincanV created
    Support Team .NET Developer

    I have resolved most of the issues, but putting some conditions in service worker.js file as that was not there in default file that was provided by ABP solution, can you please confirm the code that I have written so far, thanks

    I haven't tried your code yet, but it seems all right and covers most scenarios including showing profile images. You can customize this file to your needs without the need to worry.

Made with ❤️ on ABP v9.1.0-preview. Updated on November 11, 2024, 11:11