diff --git a/PlaylistShared.Pwa/Layout/MainLayout.razor b/PlaylistShared.Pwa/Layout/MainLayout.razor index 963ef10..e1e0be1 100644 --- a/PlaylistShared.Pwa/Layout/MainLayout.razor +++ b/PlaylistShared.Pwa/Layout/MainLayout.razor @@ -1,5 +1,7 @@ @using PlaylistShared.Pwa.Components.Global @inherits LayoutComponentBase +@inject PwaUpdateService PwaUpdateService +@inject IJSRuntime JSRuntime @@ -36,6 +38,8 @@ private bool _drawerOpen = true; private bool _isDarkMode = true; private MudTheme? _theme; + + private DotNetObjectReference? _dotNetRef; protected override void OnInitialized() { @@ -49,6 +53,15 @@ }; } + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + _dotNetRef = DotNetObjectReference.Create(PwaUpdateService); + await JSRuntime.InvokeVoidAsync("registerSWMessageHandler", _dotNetRef); + } + } + private void DrawerToggle() { _drawerOpen = !_drawerOpen; diff --git a/PlaylistShared.Pwa/Program.cs b/PlaylistShared.Pwa/Program.cs index 35c2246..e9481a1 100644 --- a/PlaylistShared.Pwa/Program.cs +++ b/PlaylistShared.Pwa/Program.cs @@ -20,6 +20,7 @@ internal class Program return new HttpClient { BaseAddress = new Uri(apiUrl) }; }); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/PlaylistShared.Pwa/Properties/launchSettings.json b/PlaylistShared.Pwa/Properties/launchSettings.json index 6d80cdc..f14a056 100644 --- a/PlaylistShared.Pwa/Properties/launchSettings.json +++ b/PlaylistShared.Pwa/Properties/launchSettings.json @@ -10,6 +10,14 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } + }, + + "https (prod)": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "https://localhost:7225;http://localhost:5181" } } } diff --git a/PlaylistShared.Pwa/Services/PwaUpdateService.cs b/PlaylistShared.Pwa/Services/PwaUpdateService.cs new file mode 100644 index 0000000..b3657ff --- /dev/null +++ b/PlaylistShared.Pwa/Services/PwaUpdateService.cs @@ -0,0 +1,33 @@ +using Microsoft.JSInterop; +using MudBlazor; + +namespace PlaylistShared.Pwa.Services; + +public class PwaUpdateService +{ + private readonly ISnackbar _snackbar; + private readonly IJSRuntime _jsRuntime; + + public PwaUpdateService(ISnackbar snackbar, IJSRuntime jsRuntime) + { + _snackbar = snackbar; + _jsRuntime = jsRuntime; + } + + [JSInvokable] + public void OnNewVersionAvailable() + { + _snackbar.Add("Доступна новая версия! Обновите страницу.", Severity.Info, configure: options => + { + options.Action = "Обновить"; + options.ShowCloseIcon = false; + options.RequireInteraction = true; + options.OnClick = _ => + { + _jsRuntime.InvokeVoidAsync("location.reload", true); + return Task.CompletedTask; + }; + options.CloseAfterNavigation = true; + }); + } +} \ No newline at end of file diff --git a/PlaylistShared.Pwa/wwwroot/index.html b/PlaylistShared.Pwa/wwwroot/index.html index 1f32af0..ca48665 100644 --- a/PlaylistShared.Pwa/wwwroot/index.html +++ b/PlaylistShared.Pwa/wwwroot/index.html @@ -168,6 +168,15 @@ + diff --git a/PlaylistShared.Pwa/wwwroot/service-worker.published.js b/PlaylistShared.Pwa/wwwroot/service-worker.published.js index 9c2fe50..7c6cd68 100644 --- a/PlaylistShared.Pwa/wwwroot/service-worker.published.js +++ b/PlaylistShared.Pwa/wwwroot/service-worker.published.js @@ -8,10 +8,10 @@ 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$/, /\.webmanifest$/ ]; -const offlineAssetsExclude = [ /^service-worker\.js$/ ]; +const offlineAssetsInclude = [/\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/, /\.webmanifest$/]; +// ИСКЛЮЧАЕМ также service-worker-assets.js +const offlineAssetsExclude = [/^service-worker\.js$/, /\/service-worker-assets\.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); @@ -19,7 +19,6 @@ const manifestUrlList = self.assetsManifest.assets.map(asset => new URL(asset.ur async function onInstall(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))) @@ -30,6 +29,12 @@ async function onInstall(event) { async function onActivate(event) { await self.clients.claim(); + // НОВОЕ: Уведомляем все открытые вкладки о том, что новый SW активирован + const clientsList = await self.clients.matchAll(); + clientsList.forEach(client => { + client.postMessage({ type: 'SW_ACTIVATED', version: self.assetsManifest.version }); + }); + // Delete unused caches const cacheKeys = await caches.keys(); await Promise.all(cacheKeys @@ -38,13 +43,16 @@ async function onActivate(event) { } async function onFetch(event) { + // НОВОЕ: никогда не перехватываем файлы Service Worker + const url = event.request.url; + if (url.includes('/service-worker.js') || url.includes('/service-worker-assets.js')) { + return fetch(event.request); + } + let cachedResponse = null; if (event.request.method === 'GET') { - // For all navigation requests, try to serve index.html from cache, - // unless that request is for an offline resource. - // If you need some URLs to be server-rendered, edit the following check to exclude those URLs const shouldServeIndexHtml = event.request.mode === 'navigate' - && !manifestUrlList.some(url => url === event.request.url); + && !manifestUrlList.some(u => u === event.request.url); const request = shouldServeIndexHtml ? 'index.html' : event.request; const cache = await caches.open(cacheName); @@ -52,4 +60,4 @@ async function onFetch(event) { } return cachedResponse || fetch(event.request); -} +} \ No newline at end of file