diff --git a/Aberwyn/Aberwyn.csproj b/Aberwyn/Aberwyn.csproj
index 33d4e20..b681382 100644
--- a/Aberwyn/Aberwyn.csproj
+++ b/Aberwyn/Aberwyn.csproj
@@ -27,6 +27,7 @@
+
@@ -51,6 +52,7 @@
+
diff --git a/Aberwyn/Controllers/FoodMenuController.cs b/Aberwyn/Controllers/FoodMenuController.cs
index 1682a63..c405fd5 100644
--- a/Aberwyn/Controllers/FoodMenuController.cs
+++ b/Aberwyn/Controllers/FoodMenuController.cs
@@ -212,7 +212,6 @@ namespace Aberwyn.Controllers
return RedirectToAction("Veckomeny", new { week, year });
}
-
-
}
+
}
diff --git a/Aberwyn/Controllers/PushController.cs b/Aberwyn/Controllers/PushController.cs
new file mode 100644
index 0000000..3c1ef73
--- /dev/null
+++ b/Aberwyn/Controllers/PushController.cs
@@ -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 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 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.");
+ }
+
+ }
+
+}
diff --git a/Aberwyn/Data/ApplicationDbContext.cs b/Aberwyn/Data/ApplicationDbContext.cs
index 96a2609..3d1558a 100644
--- a/Aberwyn/Data/ApplicationDbContext.cs
+++ b/Aberwyn/Data/ApplicationDbContext.cs
@@ -14,5 +14,6 @@ namespace Aberwyn.Data
public DbSet BudgetPeriods { get; set; }
public DbSet BudgetCategories { get; set; }
public DbSet BudgetItems { get; set; }
+ public DbSet PushSubscribers { get; set; }
}
}
diff --git a/Aberwyn/Data/BudgetContext.cs b/Aberwyn/Data/BudgetContext.cs
deleted file mode 100644
index 8ab1ac1..0000000
--- a/Aberwyn/Data/BudgetContext.cs
+++ /dev/null
@@ -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 options) : base(options) { }
-
- public DbSet BudgetItems { get; set; } // This line maps your BudgetItem model
- }
-}
\ No newline at end of file
diff --git a/Aberwyn/Data/PushNotificationService.cs b/Aberwyn/Data/PushNotificationService.cs
new file mode 100644
index 0000000..64a1b3a
--- /dev/null
+++ b/Aberwyn/Data/PushNotificationService.cs
@@ -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}");
+ }
+ }
+ }
+}
diff --git a/Aberwyn/Migrations/20250523075931_AddPushSubscribers.Designer.cs b/Aberwyn/Migrations/20250523075931_AddPushSubscribers.Designer.cs
new file mode 100644
index 0000000..ced95c5
--- /dev/null
+++ b/Aberwyn/Migrations/20250523075931_AddPushSubscribers.Designer.cs
@@ -0,0 +1,403 @@
+//
+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("Id")
+ .HasColumnType("varchar(255)");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("int");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("longtext");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("datetime(6)");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("PasswordHash")
+ .HasColumnType("longtext");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("longtext");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("longtext");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("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("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("BudgetPeriodId")
+ .HasColumnType("int");
+
+ b.Property("Color")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BudgetPeriodId");
+
+ b.ToTable("BudgetCategories");
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("Amount")
+ .HasColumnType("decimal(65,30)");
+
+ b.Property("BudgetCategoryId")
+ .HasColumnType("int");
+
+ b.Property("IncludeInSummary")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("IsExpense")
+ .HasColumnType("tinyint(1)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("BudgetCategoryId");
+
+ b.ToTable("BudgetItems");
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.BudgetPeriod", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("Month")
+ .HasColumnType("int");
+
+ b.Property("Order")
+ .HasColumnType("int");
+
+ b.Property("Year")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.ToTable("BudgetPeriods");
+ });
+
+ modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("Auth")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("Endpoint")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("P256DH")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.ToTable("PushSubscribers");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("varchar(255)");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("longtext");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("varchar(256)");
+
+ b.Property("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", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("ClaimType")
+ .HasColumnType("longtext");
+
+ b.Property("ClaimValue")
+ .HasColumnType("longtext");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("varchar(255)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("ClaimType")
+ .HasColumnType("longtext");
+
+ b.Property("ClaimValue")
+ .HasColumnType("longtext");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("varchar(255)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("varchar(255)");
+
+ b.Property("ProviderKey")
+ .HasColumnType("varchar(255)");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("longtext");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("varchar(255)");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("varchar(255)");
+
+ b.Property("RoleId")
+ .HasColumnType("varchar(255)");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("varchar(255)");
+
+ b.Property("LoginProvider")
+ .HasColumnType("varchar(255)");
+
+ b.Property("Name")
+ .HasColumnType("varchar(255)");
+
+ b.Property("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", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("Aberwyn.Models.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("Aberwyn.Models.ApplicationUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", 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", 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
+ }
+ }
+}
diff --git a/Aberwyn/Migrations/20250523075931_AddPushSubscribers.cs b/Aberwyn/Migrations/20250523075931_AddPushSubscribers.cs
new file mode 100644
index 0000000..8e2455c
--- /dev/null
+++ b/Aberwyn/Migrations/20250523075931_AddPushSubscribers.cs
@@ -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(type: "int", nullable: false)
+ .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
+ Endpoint = table.Column(type: "longtext", nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ P256DH = table.Column(type: "longtext", nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ Auth = table.Column(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");
+ }
+ }
+}
diff --git a/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs b/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs
index e9f964c..d58148b 100644
--- a/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs
+++ b/Aberwyn/Migrations/ApplicationDbContextModelSnapshot.cs
@@ -162,6 +162,29 @@ namespace Aberwyn.Migrations
b.ToTable("BudgetPeriods");
});
+ modelBuilder.Entity("Aberwyn.Models.PushSubscriber", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ b.Property("Auth")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("Endpoint")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("P256DH")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.ToTable("PushSubscribers");
+ });
+
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property("Id")
diff --git a/Aberwyn/Models/PushSubscriber.cs b/Aberwyn/Models/PushSubscriber.cs
new file mode 100644
index 0000000..f8b2a46
--- /dev/null
+++ b/Aberwyn/Models/PushSubscriber.cs
@@ -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; }
+ }
+
+}
diff --git a/Aberwyn/Models/VapidOptions.cs b/Aberwyn/Models/VapidOptions.cs
new file mode 100644
index 0000000..24e59f5
--- /dev/null
+++ b/Aberwyn/Models/VapidOptions.cs
@@ -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; }
+ }
+}
diff --git a/Aberwyn/Program.cs b/Aberwyn/Program.cs
index 2788ef6..0ce3cc7 100644
--- a/Aberwyn/Program.cs
+++ b/Aberwyn/Program.cs
@@ -53,6 +53,17 @@ builder.Services.AddControllersWithViews();
// Register your services
builder.Services.AddScoped();
+builder.Services.AddSingleton(sp =>
+{
+ var config = sp.GetRequiredService();
+ return new PushNotificationService(
+ config["VapidKeys:Subject"],
+ config["VapidKeys:PublicKey"],
+ config["VapidKeys:PrivateKey"]
+ );
+});
+
+builder.Services.Configure(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(sp =>
+{
+ var config = sp.GetRequiredService();
+ 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();
await TestDataSeeder.SeedBudget(context);
}
+
+
+
app.Run();
diff --git a/Aberwyn/Views/Admin/Index.cshtml b/Aberwyn/Views/Admin/Index.cshtml
index 317396c..a89423f 100644
--- a/Aberwyn/Views/Admin/Index.cshtml
+++ b/Aberwyn/Views/Admin/Index.cshtml
@@ -70,3 +70,37 @@
}
+
+Testa Pushnotis
+
+
+
diff --git a/Aberwyn/Views/Meal/View.cshtml b/Aberwyn/Views/Meal/View.cshtml
index c20438e..5f1c96e 100644
--- a/Aberwyn/Views/Meal/View.cshtml
+++ b/Aberwyn/Views/Meal/View.cshtml
@@ -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'
+ }
+ });
+ });
+ }
+