App and push notifications?
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Elias Jansson
2025-05-23 13:52:09 +02:00
parent 7f41c08b82
commit f8cbce3ec0
20 changed files with 796 additions and 15 deletions

View File

@@ -27,6 +27,7 @@
<ItemGroup>
<PackageReference Include="AngularJS.Core" Version="1.8.2" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.67" />
<PackageReference Include="Lib.Net.Http.WebPush" Version="3.3.1" />
<!-- Entity Framework Core 6 -->
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.36" />
@@ -51,6 +52,7 @@
<!-- Övrigt -->
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.15.1" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.18" />
<PackageReference Include="WebPush" Version="1.0.12" />
</ItemGroup>
<ItemGroup>

View File

@@ -212,7 +212,6 @@ namespace Aberwyn.Controllers
return RedirectToAction("Veckomeny", new { week, year });
}
}
}

View File

@@ -0,0 +1,87 @@
using Aberwyn.Data;
using Aberwyn.Models;
using Lib.Net.Http.WebPush;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace Aberwyn.Controllers
{
[ApiController]
[Route("api/push")]
public class PushController : ControllerBase
{
private readonly ApplicationDbContext _context;
private readonly PushNotificationService _notificationService;
public PushController(ApplicationDbContext context, PushNotificationService notificationService)
{
_context = context;
_notificationService = notificationService;
}
[HttpPost("subscribe")]
public async Task<IActionResult> Subscribe([FromBody] PushSubscription subscription)
{
var existing = await _context.PushSubscribers
.FirstOrDefaultAsync(s => s.Endpoint == subscription.Endpoint);
if (existing == null)
{
var newSubscriber = new PushSubscriber
{
Endpoint = subscription.Endpoint,
P256DH = subscription.Keys["p256dh"],
Auth = subscription.Keys["auth"]
};
_context.PushSubscribers.Add(newSubscriber);
}
else
{
existing.P256DH = subscription.Keys["p256dh"];
existing.Auth = subscription.Keys["auth"];
}
await _context.SaveChangesAsync();
return Ok();
}
[HttpGet("vapid-public-key")]
public IActionResult GetVapidKey([FromServices] IConfiguration config)
{
var publicKey = config["VapidKeys:PublicKey"];
return Content(publicKey);
}
[HttpPost("notify-all")]
public async Task<IActionResult> NotifyAll([FromBody] PushMessageDto message)
{
if (string.IsNullOrWhiteSpace(message.Title) || string.IsNullOrWhiteSpace(message.Body))
{
return BadRequest("Titel och meddelande krävs.");
}
var subscribers = await _context.PushSubscribers.ToListAsync();
var payload = $"{{\"title\":\"{message.Title}\",\"body\":\"{message.Body}\"}}";
int successCount = 0;
foreach (var sub in subscribers)
{
try
{
_notificationService.SendNotification(sub.Endpoint, sub.P256DH, sub.Auth, payload);
successCount++;
}
catch (Exception ex)
{
Console.WriteLine($"❌ Misslyckades att skicka till {sub.Endpoint}: {ex.Message}");
}
}
return Ok($"Skickade notiser till {successCount} användare.");
}
}
}

View File

@@ -14,5 +14,6 @@ namespace Aberwyn.Data
public DbSet<BudgetPeriod> BudgetPeriods { get; set; }
public DbSet<BudgetCategory> BudgetCategories { get; set; }
public DbSet<BudgetItem> BudgetItems { get; set; }
public DbSet<PushSubscriber> PushSubscribers { get; set; }
}
}

View File

@@ -1,12 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Aberwyn.Models; // Adjust this namespace as needed
namespace Aberwyn.Data
{
public class BudgetContext : DbContext
{
public BudgetContext(DbContextOptions<BudgetContext> options) : base(options) { }
public DbSet<BudgetItem> BudgetItems { get; set; } // This line maps your BudgetItem model
}
}

View File

@@ -0,0 +1,31 @@
using WebPush;
using System;
namespace Aberwyn.Data
{
public class PushNotificationService
{
private readonly VapidDetails _vapidDetails;
private readonly WebPushClient _client;
public PushNotificationService(string subject, string publicKey, string privateKey)
{
_vapidDetails = new VapidDetails(subject, publicKey, privateKey);
_client = new WebPushClient();
}
public void SendNotification(string endpoint, string p256dh, string auth, string payload)
{
var subscription = new PushSubscription(endpoint, p256dh, auth);
try
{
_client.SendNotification(subscription, payload, _vapidDetails);
}
catch (WebPushException ex)
{
Console.WriteLine($"❌ Push-fel: {ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,403 @@
// <auto-generated />
using System;
using Aberwyn.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Aberwyn.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20250523075931_AddPushSubscribers")]
partial class AddPushSubscribers
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "6.0.36")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Aberwyn.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("tinyint(1)");
b.Property<bool>("LockoutEnabled")
.HasColumnType("tinyint(1)");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetime(6)");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("longtext");
b.Property<string>("PhoneNumber")
.HasColumnType("longtext");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("tinyint(1)");
b.Property<string>("SecurityStamp")
.HasColumnType("longtext");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("tinyint(1)");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("BudgetPeriodId")
.HasColumnType("int");
b.Property<string>("Color")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Order")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("BudgetPeriodId");
b.ToTable("BudgetCategories");
});
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<decimal>("Amount")
.HasColumnType("decimal(65,30)");
b.Property<int>("BudgetCategoryId")
.HasColumnType("int");
b.Property<bool>("IncludeInSummary")
.HasColumnType("tinyint(1)");
b.Property<bool>("IsExpense")
.HasColumnType("tinyint(1)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<int>("Order")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("BudgetCategoryId");
b.ToTable("BudgetItems");
});
modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("Month")
.HasColumnType("int");
b.Property<int>("Order")
.HasColumnType("int");
b.Property<int>("Year")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("BudgetPeriods");
});
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Auth")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Endpoint")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("P256DH")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("PushSubscribers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("longtext");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("varchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ClaimType")
.HasColumnType("longtext");
b.Property<string>("ClaimValue")
.HasColumnType("longtext");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderKey")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("longtext");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("varchar(255)");
b.Property<string>("RoleId")
.HasColumnType("varchar(255)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("varchar(255)");
b.Property<string>("LoginProvider")
.HasColumnType("varchar(255)");
b.Property<string>("Name")
.HasColumnType("varchar(255)");
b.Property<string>("Value")
.HasColumnType("longtext");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
{
b.HasOne("Aberwyn.Models.BudgetPeriod", "BudgetPeriod")
.WithMany("Categories")
.HasForeignKey("BudgetPeriodId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("BudgetPeriod");
});
modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
{
b.HasOne("Aberwyn.Models.BudgetCategory", "BudgetCategory")
.WithMany("Items")
.HasForeignKey("BudgetCategoryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("BudgetCategory");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Aberwyn.Models.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Aberwyn.Models.BudgetCategory", b =>
{
b.Navigation("Items");
});
modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b =>
{
b.Navigation("Categories");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,38 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Aberwyn.Migrations
{
public partial class AddPushSubscribers : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "PushSubscribers",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Endpoint = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
P256DH = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Auth = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_PushSubscribers", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "PushSubscribers");
}
}
}

View File

@@ -162,6 +162,29 @@ namespace Aberwyn.Migrations
b.ToTable("BudgetPeriods");
});
modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Auth")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Endpoint")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("P256DH")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("PushSubscribers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")

View File

@@ -0,0 +1,16 @@
namespace Aberwyn.Models
{
public class PushSubscriber
{
public int Id { get; set; }
public string Endpoint { get; set; }
public string P256DH { get; set; }
public string Auth { get; set; }
}
public class PushMessageDto
{
public string Title { get; set; }
public string Body { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
namespace Aberwyn.Models
{
public class VapidOptions
{
public string Subject { get; set; }
public string PublicKey { get; set; }
public string PrivateKey { get; set; }
}
}

View File

@@ -53,6 +53,17 @@ builder.Services.AddControllersWithViews();
// Register your services
builder.Services.AddScoped<MenuService>();
builder.Services.AddSingleton<PushNotificationService>(sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
return new PushNotificationService(
config["VapidKeys:Subject"],
config["VapidKeys:PublicKey"],
config["VapidKeys:PrivateKey"]
);
});
builder.Services.Configure<VapidOptions>(builder.Configuration.GetSection("Vapid"));
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
@@ -67,6 +78,15 @@ builder.Configuration
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
builder.Services.AddSingleton<PushNotificationService>(sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
return new PushNotificationService(
config["VapidKeys:Subject"],
config["VapidKeys:PublicKey"],
config["VapidKeys:PrivateKey"]
);
});
var app = builder.Build();
@@ -105,4 +125,7 @@ using (var scope = app.Services.CreateScope())
var context = services.GetRequiredService<ApplicationDbContext>();
await TestDataSeeder.SeedBudget(context);
}
app.Run();

View File

@@ -70,3 +70,37 @@
}
</tbody>
</table>
<h3>Testa Pushnotis</h3>
<form onsubmit="sendPush(event)">
<div class="form-group">
<label for="title">Titel:</label>
<input type="text" id="title" name="title" class="form-control" required />
</div>
<div class="form-group">
<label for="body">Meddelande:</label>
<input type="text" id="body" name="body" class="form-control" required />
</div>
<button type="submit" class="btn btn-warning mt-2">Skicka testnotis</button>
</form>
<script>
async function sendPush(event) {
event.preventDefault(); // 🚫 stoppa formuläret från att göra vanlig POST
const title = document.getElementById("title").value;
const body = document.getElementById("body").value;
const response = await fetch("/api/push/notify-all", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title, body })
});
if (response.ok) {
alert("✅ Pushnotis skickad!");
} else {
alert("❌ Något gick fel.");
}
}
</script>

View File

@@ -223,6 +223,36 @@
});
}
});
if ('serviceWorker' in navigator && 'PushManager' in window) {
navigator.serviceWorker.register('/service-worker.js')
.then(function (registration) {
console.log('Service Worker registered', registration);
return registration.pushManager.getSubscription()
.then(async function (subscription) {
if (subscription) {
console.log('Already subscribed to push notifications.');
return subscription;
}
const response = await fetch('/api/push/vapid-public-key');
const vapidPublicKey = await response.text();
const convertedVapidKey = urlBase64ToUint8Array(vapidPublicKey);
return registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: convertedVapidKey
});
});
}).then(function (subscription) {
return fetch('/api/push/subscribe', {
method: 'POST',
body: JSON.stringify(subscription),
headers: {
'Content-Type': 'application/json'
}
});
});
}
</script>
<style>

View File

@@ -1,6 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="manifest" href="/manifest.json">
<link rel="icon" type="image/png" sizes="512x512" href="/images/lewel-icon.png">
<meta name="theme-color" content="#6a0dad">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LEWEL - Dashboard</title>

View File

@@ -9,5 +9,10 @@
"ConnectionStrings": {
"DefaultConnection": "Server=192.168.1.108;Database=Nevyn;Uid=root;Pwd=3edc4RFV;",
"ProductionConnection": "Server=192.168.1.108;Database=Nevyn;Uid=root;Pwd=3edc4RFV;"
},
"Vapid": {
"Subject": "mailto:e@zcz.se",
"PublicKey": "BBLmMdU3X3e79SqzAy4vIAJI0jmzRME17F9UKbO8XT1dfnO-mWIPKIrFDbIZD4_3ic7uoijK61vaGdfFUk3HUfU",
"PrivateKey": "oranoCmCo8HXdc03juNgbeSlKE39N3DYus_eMunLsnc"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -1,4 +1,42 @@
// Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
// for details on configuring this project to bundle and minify static web assets.
// Write your JavaScript code.
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/service-worker.js')
.then(function (registration) {
console.log('✅ Service Worker registrerad med scope:', registration.scope);
subscribeToPush().catch(console.error);
})
.catch(function (error) {
console.log('❌ Service Worker-registrering misslyckades:', error);
});
});
}
async function subscribeToPush() {
const registration = await navigator.serviceWorker.ready;
const publicVapidKey = await fetch('/api/push/vapid-public-key').then(r => r.text());
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(publicVapidKey)
});
await fetch('/api/push/subscribe', {
method: 'POST',
body: JSON.stringify(subscription),
headers: { 'Content-Type': 'application/json' }
});
console.log('✅ Push-prenumeration skickad');
}
// utility för att konvertera nyckeln
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
const raw = atob(base64);
return new Uint8Array([...raw].map(char => char.charCodeAt(0)));
}

View File

@@ -0,0 +1,15 @@
{
"name": "LEWEL",
"short_name": "LEWEL",
"start_url": "/",
"display": "standalone",
"background_color": "#1F2C3C",
"theme_color": "#6a0dad",
"icons": [
{
"src": "/images/lewel-icon.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

View File

@@ -0,0 +1,36 @@
const CACHE_NAME = 'lewel-cache-v1';
const urlsToCache = [
'/',
'/css/site.css',
'/js/site.js',
'/icons/lewel-icon.png',
'/manifest.json'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
});
self.addEventListener('push', function (event) {
const data = event.data ? event.data.json() : { title: 'LEWEL', body: 'Ny notis!' };
const options = {
body: data.body,
icon: '/icons/lewel-icon.png',
badge: '/icons/lewel-icon.png'
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});