push service
This commit is contained in:
@@ -10,3 +10,5 @@ src/cert.pem
|
||||
src/key.pem
|
||||
|
||||
storage
|
||||
|
||||
src/services/push/vapid.json
|
||||
|
||||
@@ -13,4 +13,4 @@ GRANT DELETE, INSERT, SELECT, UPDATE ON TABLES TO webapi;
|
||||
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public
|
||||
GRANT USAGE ON TYPES TO webapi;
|
||||
|
||||
--GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO webapi;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"postinstall": "playwright install"
|
||||
},
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@10.31.0+sha512.e3927388bfaa8078ceb79b748ffc1e8274e84d75163e67bc22e06c0d3aed43dd153151cbf11d7f8301ff4acb98c68bdc5cadf6989532801ffafe3b3e4a63c268",
|
||||
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@types/connect-pg-simple": "^7.0.3",
|
||||
@@ -17,6 +17,7 @@
|
||||
"@types/express-session": "^1.18.2",
|
||||
"@types/node": "^25.3.5",
|
||||
"@types/pg": "^8.18.0",
|
||||
"@types/web-push": "^3.6.4",
|
||||
"prisma": "^7.4.2",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "~5.9.3"
|
||||
@@ -37,6 +38,7 @@
|
||||
"pg": "^8.20.0",
|
||||
"playwright": "^1.58.2",
|
||||
"rxjs": "~7.8.2",
|
||||
"tsoa": "^6.6.0"
|
||||
"tsoa": "^6.6.0",
|
||||
"web-push": "^3.6.7"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "PushSubscription" (
|
||||
"id" UUID NOT NULL DEFAULT uuidv7(),
|
||||
"endpoint" VARCHAR NOT NULL,
|
||||
"expirationTime" DOUBLE PRECISION,
|
||||
"userSubscriptionId" UUID,
|
||||
"p256dh" VARCHAR NOT NULL,
|
||||
"auth" VARCHAR NOT NULL,
|
||||
"userId" VARCHAR NOT NULL,
|
||||
|
||||
CONSTRAINT "PushSubscription_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "PushSubscription_userSubscriptionId_key" ON "PushSubscription"("userSubscriptionId");
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `userSubscriptionId` on the `PushSubscription` table. All the data in the column will be lost.
|
||||
- Added the required column `clientId` to the `PushSubscription` table without a default value. This is not possible if the table is not empty.
|
||||
- Added the required column `topic` to the `PushSubscription` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- DropIndex
|
||||
DROP INDEX "PushSubscription_userSubscriptionId_key";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "PushSubscription" DROP COLUMN "userSubscriptionId",
|
||||
ADD COLUMN "clientId" UUID NOT NULL,
|
||||
ADD COLUMN "topic" "Topic" NOT NULL,
|
||||
ADD COLUMN "topicConfiguration" JSONB;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" VARCHAR NOT NULL,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "PushSubscription" ADD CONSTRAINT "PushSubscription_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -21,7 +21,7 @@ model Member {
|
||||
kontakt String? @db.VarChar
|
||||
kuendigungzum DateTime? @db.Date
|
||||
marker Boolean?
|
||||
creationDate DateTime? @db.Date @default(now())
|
||||
creationDate DateTime? @default(now()) @db.Date
|
||||
}
|
||||
|
||||
model session {
|
||||
@@ -37,13 +37,13 @@ enum Topic {
|
||||
}
|
||||
|
||||
model FoundEvents {
|
||||
id String @id @default(dbgenerated("uuidv7()")) @db.Uuid
|
||||
source String @db.VarChar
|
||||
url String @db.VarChar
|
||||
foundDate DateTime @db.Date @default(now())
|
||||
eventDate String? @db.VarChar
|
||||
title String? @db.VarChar
|
||||
description String? @db.VarChar
|
||||
id String @id @default(dbgenerated("uuidv7()")) @db.Uuid
|
||||
source String @db.VarChar
|
||||
url String @db.VarChar
|
||||
foundDate DateTime @default(now()) @db.Date
|
||||
eventDate String? @db.VarChar
|
||||
title String? @db.VarChar
|
||||
description String? @db.VarChar
|
||||
attachedFiles FoundEventAttachments[]
|
||||
}
|
||||
|
||||
@@ -53,3 +53,24 @@ model FoundEventAttachments {
|
||||
event FoundEvents @relation(fields: [eventId], references: [id])
|
||||
eventId String @db.Uuid
|
||||
}
|
||||
|
||||
model PushSubscription {
|
||||
id String @id @default(dbgenerated("uuidv7()")) @db.Uuid
|
||||
endpoint String @db.VarChar
|
||||
expirationTime Float?
|
||||
p256dh String @db.VarChar
|
||||
auth String @db.VarChar
|
||||
|
||||
userId String @db.VarChar
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
topic Topic
|
||||
topicConfiguration Json? @db.JsonB
|
||||
clientId String @db.Uuid
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @db.VarChar @id
|
||||
subscriptions PushSubscription[]
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import express from "express";
|
||||
import { Body, Controller, Post, Query, Request, Route, Security, SuccessResponse } from "tsoa";
|
||||
import type { PushSubscriptionCreateArgs as PushSubscriptionCreateArgsDto } from "../dtos/pushSubscription.js";
|
||||
import { inject } from "../infrastructure/di/injector.js";
|
||||
import { KC_SECURITY_NAME } from "../services/keycloak/user/keycloak-user.js";
|
||||
import { PushService } from "../services/push/pushService.js";
|
||||
|
||||
@Route("push")
|
||||
export class PushContoller extends Controller {
|
||||
private readonly pushService = inject(PushService);
|
||||
|
||||
@Post("add")
|
||||
@Security(KC_SECURITY_NAME)
|
||||
@SuccessResponse("200", "OK")
|
||||
public async addSubscription(
|
||||
@Body() subscription: PushSubscriptionCreateArgsDto,
|
||||
@Request() request: express.Request,
|
||||
): Promise<void> {
|
||||
return await this.pushService.storeSubscription(subscription, request, request.res);
|
||||
}
|
||||
|
||||
@Post('reset')
|
||||
@Security(KC_SECURITY_NAME)
|
||||
@SuccessResponse('200', 'OK')
|
||||
public async resetSubscriptions(
|
||||
@Request() request: express.Request
|
||||
): Promise<void>{
|
||||
return await this.pushService.reset(request);
|
||||
}
|
||||
|
||||
@Post('clearSingle')
|
||||
@Security(KC_SECURITY_NAME)
|
||||
@SuccessResponse("200", 'OK')
|
||||
public async clearSubscription(
|
||||
@Request() request: express.Request,
|
||||
@Query() clientId: string
|
||||
):Promise<void>{
|
||||
await this.pushService.clearSubscription(clientId, request);
|
||||
}
|
||||
|
||||
@Post('test-publish')
|
||||
@Security(KC_SECURITY_NAME)
|
||||
@SuccessResponse("200", "OK")
|
||||
public async publishTestMessage(
|
||||
@Request() request: express.Request
|
||||
): Promise<void>{
|
||||
this.pushService.sendtestNotification(request);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,11 +13,11 @@ import { Claim } from "../dtos/claim.js";
|
||||
import { type User } from "../dtos/user.js";
|
||||
import { inject } from "../infrastructure/di/index.js";
|
||||
import { KC_SECURITY_NAME } from "../services/keycloak/user/index.js";
|
||||
import { UserService } from "../services/userService.js";
|
||||
import { UserAdminService } from "../services/userAdminService.js";
|
||||
|
||||
@Route("users")
|
||||
export class UsersController extends Controller {
|
||||
private readonly userService = inject(UserService);
|
||||
private readonly userService = inject(UserAdminService);
|
||||
|
||||
@Security(KC_SECURITY_NAME, [Claim.UserAdmin])
|
||||
@SuccessResponse("200", "OK")
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { JsonValue } from "@prisma/client/runtime/client";
|
||||
|
||||
export class PushSubscriptionDto{
|
||||
id!: string;
|
||||
endpoint!: string;
|
||||
expirationTime!: number | null;
|
||||
keys!: {
|
||||
p256dh: string;
|
||||
auth: string;
|
||||
};
|
||||
topic!: "TEST";
|
||||
topicConfiguration!: JsonValue | null;
|
||||
clientId!: string;
|
||||
}
|
||||
|
||||
export class PushSubscriptionCreateArgs/* implements Omit<PushSubscriptionDto, "id">*/{
|
||||
endpoint!: string;
|
||||
expirationTime!: number | null;
|
||||
keys!: { p256dh: string; auth: string; };
|
||||
topic!: "TEST";
|
||||
topicConfiguration!: string | null;
|
||||
clientId!: string;
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { PrismaPg } from "@prisma/adapter-pg";
|
||||
import { type Request } from "express";
|
||||
import { PrismaClient } from "../../generated/prisma/client.js";
|
||||
import type { UserModel } from "../../generated/prisma/models.js";
|
||||
import { Injectable } from "../../infrastructure/di/injectable-decorator.js";
|
||||
|
||||
type ExtendedClient = ReturnType<typeof createExtendedClient>;
|
||||
@@ -23,7 +24,17 @@ export type SafeClient = Omit<
|
||||
>;
|
||||
|
||||
function createExtendedClient(client: PrismaClient) {
|
||||
return client;
|
||||
return client.$extends({
|
||||
client: {
|
||||
async ensureUserKnown(userId: string): Promise<UserModel>{
|
||||
let user = await client.user.findUnique({where: {id: userId}});
|
||||
if(user==null){
|
||||
user = await client.user.create({data:{id: userId}});
|
||||
}
|
||||
return user
|
||||
}
|
||||
}
|
||||
});
|
||||
// .$extends({
|
||||
// name: "testExtension",
|
||||
// model: {
|
||||
|
||||
@@ -31,6 +31,9 @@ export class KeycloakUser {
|
||||
this.keycloakConnect = new Promise(async resolve => {
|
||||
const config = await this.getConfig();
|
||||
const client = new KeycloakConnect({ store: sessionStore }, config);
|
||||
client.authenticated=(r)=>{
|
||||
console.dir(r);
|
||||
};
|
||||
resolve(client);
|
||||
});
|
||||
}
|
||||
@@ -49,4 +52,10 @@ export class KeycloakUser {
|
||||
const client = await this.keycloakConnect;
|
||||
return client.middleware();
|
||||
}
|
||||
|
||||
public async getUid(request: Request, response?: Response): Promise<string>{
|
||||
const client = await this.keycloakConnect;
|
||||
const grant = await client.getGrant(request, response ?? ({} as any));
|
||||
return (grant.access_token as any)?.content?.sub;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import { DbNull } from "@prisma/client/runtime/client";
|
||||
import type { Request, Response } from "express";
|
||||
import webpush from "web-push";
|
||||
import type { PushSubscriptionCreateArgs } from "../../dtos/pushSubscription.js";
|
||||
import { Topic } from "../../generated/prisma/enums.js";
|
||||
import { inject } from "../../infrastructure/di/index.js";
|
||||
import { Injectable } from "../../infrastructure/di/injectable-decorator.js";
|
||||
import { DatabaseService } from "../db/prisma.js";
|
||||
import { KeycloakUser } from "../keycloak/user/keycloak-user.js";
|
||||
import VAPID from './vapid.json' with { type: "json" };
|
||||
|
||||
@Injectable()
|
||||
export class PushService{
|
||||
private readonly db = inject(DatabaseService);
|
||||
private readonly userService = inject(KeycloakUser);
|
||||
|
||||
public constructor(){
|
||||
webpush.setVapidDetails("mailto:toniwalter.blue@gmail.com", VAPID.publicKey, VAPID.privateKey);
|
||||
}
|
||||
|
||||
public async storeSubscription(subscription: PushSubscriptionCreateArgs, request: Request, response?: Response): Promise<void>{
|
||||
const userId= await this.userService.getUid(request, response);
|
||||
await this.db.doRequest(async prisma=>{
|
||||
await prisma.ensureUserKnown(userId);
|
||||
return await prisma.pushSubscription.create({
|
||||
data: {
|
||||
auth: subscription.keys.auth,
|
||||
endpoint: subscription.endpoint,
|
||||
p256dh: subscription.keys.p256dh,
|
||||
expirationTime: subscription.expirationTime ?? null,
|
||||
userId: userId,
|
||||
clientId: subscription.clientId,
|
||||
topic: subscription.topic,
|
||||
topicConfiguration: subscription.topicConfiguration ? JSON.parse(subscription.topicConfiguration) : DbNull,
|
||||
}
|
||||
});
|
||||
}, request);
|
||||
}
|
||||
|
||||
public async sendtestNotification(request: Request){
|
||||
const subscriptions = await this.db.doRequest(async prisma=>prisma.pushSubscription.findMany({where:{topic: "TEST"}}), request);
|
||||
|
||||
const payload = {
|
||||
notification: {
|
||||
title: "ATitle",
|
||||
body: "DatBod",
|
||||
icon: "assets/some-icon.png",
|
||||
vibrate: [100, 50, 100],
|
||||
data: {
|
||||
dateOfArrival: Date.now(),
|
||||
primaryKey: 1
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
action: "explode",
|
||||
title: "Make Boom"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`sending ${subscriptions.length} test messages`);
|
||||
for(const s of subscriptions){
|
||||
const subscription: webpush.PushSubscription={
|
||||
endpoint: s.endpoint,
|
||||
keys: {
|
||||
auth: s.auth,
|
||||
p256dh: s.p256dh
|
||||
},
|
||||
expirationTime: s.expirationTime
|
||||
};
|
||||
try{
|
||||
const result = await webpush.sendNotification(subscription, JSON.stringify(payload), {
|
||||
topic: Topic.TEST,
|
||||
});
|
||||
console.log("success");
|
||||
console.dir(result);
|
||||
}catch(error){
|
||||
console.error("Cannot send notification: ", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async reset(request: Request): Promise<void> {
|
||||
const userId=await this.userService.getUid(request, request.res);
|
||||
const subscriptions = await this.db.doRequest(async prisma=>await prisma.pushSubscription.deleteMany({
|
||||
where:{userId: userId}
|
||||
}), request);
|
||||
|
||||
console.log(`deleted ${subscriptions.count} subs for ${userId}`);
|
||||
}
|
||||
|
||||
public async clearSubscription(clientId: string, request: Request): Promise<number> {
|
||||
const userId =await this.userService.getUid(request, request.res);
|
||||
const batchResult =await this.db.doRequest(async prisma=>await prisma.pushSubscription.deleteMany({
|
||||
where: {
|
||||
userId: userId,
|
||||
AND: {
|
||||
clientId: clientId
|
||||
}
|
||||
}
|
||||
}), request);
|
||||
return batchResult.count;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { type User } from "../dtos/user.js";
|
||||
import { inject } from "../infrastructure/di/index.js";
|
||||
import { KeycloakAdmin } from "./keycloak/admin/index.js";
|
||||
|
||||
export class UserService {
|
||||
export class UserAdminService {
|
||||
private readonly keycloakAdmin = inject(KeycloakAdmin);
|
||||
public async setClaim(
|
||||
uid: string,
|
||||
@@ -54,7 +54,9 @@
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"preserveWatchOutput": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"files": ["./src/index.ts"],
|
||||
"include": ["src", "./package.json"]
|
||||
|
||||
@@ -44,3 +44,5 @@ Thumbs.db
|
||||
|
||||
###
|
||||
src/app/generated-api/
|
||||
|
||||
src/app/services/push/vapid.json
|
||||
|
||||
@@ -1,91 +1,92 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"cli": {
|
||||
"packageManager": "pnpm"
|
||||
},
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"frontend": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "scss"
|
||||
}
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular/build:application",
|
||||
"options": {
|
||||
"browser": "src/main.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": ["src/styles.scss"]
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kB",
|
||||
"maximumError": "1MB"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "4kB",
|
||||
"maximumError": "8kB"
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"cli": {
|
||||
"packageManager": "pnpm"
|
||||
},
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"frontend": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "scss"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all",
|
||||
"serviceWorker": "ngsw-config.json"
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular/build:application",
|
||||
"options": {
|
||||
"browser": "src/main.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": ["src/styles.scss"]
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kB",
|
||||
"maximumError": "1MB"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "4kB",
|
||||
"maximumError": "8kB"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all",
|
||||
"serviceWorker": "ngsw-config.json"
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"serviceWorker": "ngsw-config.json"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular/build:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "frontend:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "frontend:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular/build:extract-i18n"
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular/build:karma",
|
||||
"options": {
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": ["src/styles.scss"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular/build:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "frontend:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "frontend:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular/build:extract-i18n"
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular/build:karma",
|
||||
"options": {
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": ["src/styles.scss"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,8 @@
|
||||
"keycloak-angular": "^21.0.0",
|
||||
"keycloak-js": "^26.2.3",
|
||||
"rxjs": "^7.8.2",
|
||||
"tslib": "^2.8.1"
|
||||
"tslib": "^2.8.1",
|
||||
"uuid": "^13.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/build": "^21.2.1",
|
||||
|
||||
@@ -86,7 +86,7 @@ export const appConfig: ApplicationConfig = {
|
||||
{ provide: MAT_DATE_FORMATS, useValue: MAT_DATE_FNS_FORMATS },
|
||||
{ provide: MAT_DATE_LOCALE, useValue: de },
|
||||
provideServiceWorker('ngsw-worker.js', {
|
||||
enabled: !isDevMode(),
|
||||
enabled: true,//!isDevMode(),
|
||||
registrationStrategy: 'registerWhenStable:30000',
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<h1>Testeite für interne Berechtigung</h1>
|
||||
<p>API: {{apiStatus()}}</p>
|
||||
<p>Auth: {{authTest()}}</p>
|
||||
<button (click)="pushService.subscribeToEvents()">Subscribe</button>
|
||||
<button (click)="pushService.resetSubscriptions()">Unsubscribe</button>
|
||||
<button (click)="pushService.publishTestMessage()">TestMessage</button>
|
||||
Claim:
|
||||
<ul>
|
||||
@let claims = this.auth.claims();
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '@angular/core';
|
||||
import * as api from '../../generated-api/api';
|
||||
import { Authentication } from '../../services/authentication';
|
||||
import { PushService } from '../../services/push/pushService';
|
||||
|
||||
@Component({
|
||||
selector: 'app-test',
|
||||
@@ -17,6 +18,7 @@ import { Authentication } from '../../services/authentication';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class Test {
|
||||
protected readonly pushService = inject(PushService);
|
||||
protected auth = inject(Authentication);
|
||||
|
||||
protected apiStatus = signal<string>('Waiting');
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { SwPush } from '@angular/service-worker';
|
||||
import { v7 as uuidv7 } from 'uuid';
|
||||
import * as api from '../../generated-api/api';
|
||||
import VAPID from './vapid.json' with { type: "json" };
|
||||
|
||||
const SUBSCRIPTION_CLIENT_ID="subscriptionClientId";
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class PushService{
|
||||
private readonly swPush=inject(SwPush);
|
||||
|
||||
private arrayBufferToString(buffer: ArrayBuffer|null){
|
||||
if(buffer==null){
|
||||
return '';
|
||||
}
|
||||
let binary = '';
|
||||
const bytes = new Uint8Array(buffer);
|
||||
const len = bytes.length;
|
||||
for(let i = 0; i< len; i++){
|
||||
binary+=String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return btoa(binary);
|
||||
}
|
||||
|
||||
public async subscribeToEvents(){
|
||||
try{
|
||||
const subscription = await this.swPush.requestSubscription({
|
||||
serverPublicKey: VAPID.publicKey
|
||||
});
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
const previousCliendId = localStorage.getItem(SUBSCRIPTION_CLIENT_ID);
|
||||
if(previousCliendId){
|
||||
try{
|
||||
await api.clearSubscription(previousCliendId);
|
||||
console.log("previous subscription cleared");
|
||||
}catch{}
|
||||
}
|
||||
|
||||
const newClientId= uuidv7();
|
||||
|
||||
await api.addSubscription({
|
||||
endpoint: subscription.endpoint,
|
||||
keys: {
|
||||
auth: this.arrayBufferToString(subscription.getKey('auth')),
|
||||
p256dh: this.arrayBufferToString(subscription.getKey('p256dh')),
|
||||
},
|
||||
expirationTime: subscription.expirationTime,
|
||||
topic: api.Topic.Test,
|
||||
topicConfiguration: null,
|
||||
clientId: newClientId,
|
||||
});
|
||||
|
||||
localStorage.setItem(SUBSCRIPTION_CLIENT_ID, newClientId);
|
||||
}catch(error){
|
||||
console.error("Subscribing failed: ", error);
|
||||
}
|
||||
}
|
||||
|
||||
public async resetSubscriptions(){
|
||||
await this.swPush.unsubscribe();
|
||||
await api.resetSubscriptions();
|
||||
}
|
||||
|
||||
public async publishTestMessage(){
|
||||
await api.publishTestMessage();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user