Checkpoint: Problem with worker metdata for VapidKeys
This commit is contained in:
24
package-lock.json
generated
24
package-lock.json
generated
@@ -2991,9 +2991,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001520",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001520.tgz",
|
||||
"integrity": "sha512-tahF5O9EiiTzwTUqAeFjIZbn4Dnqxzz7ktrgGlMYNLH43Ul26IgTMH/zvL3DG0lZxBYnlT04axvInszUsZULdA==",
|
||||
"version": "1.0.30001521",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001521.tgz",
|
||||
"integrity": "sha512-fnx1grfpEOvDGH+V17eccmNjucGUnCbP6KL+l5KqBIerp26WK/+RQ7CIDE37KGJjaPyqWXXlFUyKiWmvdNNKmQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -3596,9 +3596,9 @@
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.491",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.491.tgz",
|
||||
"integrity": "sha512-ZzPqGKghdVzlQJ+qpfE+r6EB321zed7e5JsvHIlMM4zPFF8okXUkF5Of7h7F3l3cltPL0rG7YVmlp5Qro7RQLA==",
|
||||
"version": "1.4.492",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.492.tgz",
|
||||
"integrity": "sha512-36K9b/6skMVwAIEsC7GiQ8I8N3soCALVSHqWHzNDtGemAcI9Xu8hP02cywWM0A794rTHm0b0zHPeLJHtgFVamQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/elliptic": {
|
||||
@@ -5279,9 +5279,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jackspeak": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.3.tgz",
|
||||
"integrity": "sha512-pF0kfjmg8DJLxDrizHoCZGUFz4P4czQ3HyfW4BU0ffebYkzAVlBywp5zaxW/TM+r0sGbmrQdi8EQQVTJFxnGsQ==",
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.0.tgz",
|
||||
"integrity": "sha512-uKmsITSsF4rUWQHzqaRUuyAir3fZfW3f202Ee34lz/gZCi970CPZwyQXLGNgWJvvZbvFyzeyGq0+4fcG/mBKZg==",
|
||||
"dependencies": {
|
||||
"@isaacs/cliui": "^8.0.2"
|
||||
},
|
||||
@@ -7297,9 +7297,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.1.tgz",
|
||||
"integrity": "sha512-fcOWSnnpCrovBsmFZIGIy9UqK2FaI7Hqax+DIO0A9UxeVoY4iweyaFjS5TavZN97Hfehph0nhsZnjlVKzEQSrQ==",
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.2.tgz",
|
||||
"integrity": "sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
|
||||
14
src/VapidKeys.ts
Normal file
14
src/VapidKeys.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
// Subscription.ts
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
export class VapidKeys {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
publicKey: string;
|
||||
|
||||
@Column()
|
||||
privateKey: string;
|
||||
}
|
||||
24
src/db.ts
24
src/db.ts
@@ -1,10 +1,13 @@
|
||||
|
||||
import { DataSource, Repository } from "typeorm";
|
||||
import {Subscription} from './Subscription.js'
|
||||
import { VapidKeys } from './VapidKeys.js';
|
||||
|
||||
export class DBService {
|
||||
private dataSource: DataSource;
|
||||
private vapidSource: DataSource;
|
||||
private subscriptionRepository: Repository<Subscription>;
|
||||
private vapidRepository: Repository<VapidKeys>;
|
||||
|
||||
constructor() {
|
||||
this.dataSource = new DataSource({
|
||||
@@ -12,7 +15,15 @@ export class DBService {
|
||||
database: "subscriptions",
|
||||
entities: [Subscription],
|
||||
});
|
||||
this.dataSource.getRepository(Subscription)
|
||||
this.subscriptionRepository = this.dataSource.getRepository(Subscription)
|
||||
|
||||
this.vapidSource = new DataSource({
|
||||
type: "sqlite",
|
||||
database: "vapidKeys",
|
||||
entities: [VapidKeys],
|
||||
});
|
||||
|
||||
this.vapidRepository = this.vapidSource.getRepository(VapidKeys);
|
||||
}
|
||||
|
||||
async saveSubscription(endpoint: string, keys_p256dh: string, keys_auth: string) {
|
||||
@@ -26,4 +37,15 @@ export class DBService {
|
||||
async getSubscriptions(): Promise<Subscription[]> {
|
||||
return this.subscriptionRepository.find();
|
||||
}
|
||||
|
||||
async getVapidKeys(): Promise<VapidKeys[]> {
|
||||
return this.vapidRepository.find();
|
||||
}
|
||||
|
||||
async saveVapidKeys(publicKey: string, privateKey: string) {
|
||||
const vapidkeys = new VapidKeys();
|
||||
vapidkeys.privateKey = privateKey;
|
||||
vapidkeys.publicKey = publicKey;
|
||||
return this.vapidRepository.save(vapidkeys);
|
||||
}
|
||||
}
|
||||
|
||||
10
src/main.ts
10
src/main.ts
@@ -1,4 +1,4 @@
|
||||
import { Subscription, SubscriptionService } from './subscriptionService.js';
|
||||
import { SubscriptionService } from './subscriptionService.js';
|
||||
import express, { Express, Request, Response } from 'express';
|
||||
import { Worker } from 'worker_threads';
|
||||
import { fileURLToPath } from 'url';
|
||||
@@ -7,6 +7,14 @@ import { dirname, join } from 'path';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
export interface Subscription {
|
||||
endpoint: string;
|
||||
keys: {
|
||||
p256dh: string;
|
||||
auth: string;
|
||||
};
|
||||
}
|
||||
|
||||
class Server {
|
||||
private app: Express;
|
||||
private port: number;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { SubscriptionService, Subscription } from './subscriptionService.js';
|
||||
import { VapidService, VapidKeys } from './vapidService.js';
|
||||
import { SubscriptionService } from './subscriptionService.js';
|
||||
import { VapidService } from './vapidService.js';
|
||||
import { VapidKeys } from './VapidKeys.js';
|
||||
import * as https from 'https';
|
||||
import * as http_ece from 'http_ece';
|
||||
import crypto from 'crypto';
|
||||
import { Subscription } from "./Subscription.js"
|
||||
|
||||
export interface Message {
|
||||
title: string;
|
||||
@@ -14,7 +16,6 @@ export class NotificationService {
|
||||
|
||||
private subscriptionService: SubscriptionService;
|
||||
private vapidService: VapidService;
|
||||
private vapidKeys: VapidKeys;
|
||||
|
||||
constructor() {
|
||||
this.subscriptionService = new SubscriptionService();
|
||||
@@ -27,7 +28,6 @@ export class NotificationService {
|
||||
|
||||
async broadcast(message: Message): Promise<void> {
|
||||
const subscriptions = await this.subscriptionService.fetchSubscriptions();
|
||||
this.vapidKeys = await this.vapidService.getVapidKeys();
|
||||
|
||||
for (const subscription of subscriptions) {
|
||||
await this.pushToEndpoint(subscription, message);
|
||||
@@ -35,14 +35,13 @@ export class NotificationService {
|
||||
}
|
||||
|
||||
async sendNotification(subscription: Subscription, message: Message) {
|
||||
this.vapidKeys = await this.vapidService.getVapidKeys();
|
||||
await this.pushToEndpoint(subscription, message);
|
||||
}
|
||||
|
||||
private async pushToEndpoint(subscription: Subscription, message: Message): Promise<void> {
|
||||
const payload = JSON.stringify(message);
|
||||
|
||||
const encrypted = this.encrypt(subscription.keys.p256dh, subscription.keys.auth, payload);
|
||||
const encrypted = this.encrypt(subscription.keys_p256dh, subscription.keys_auth, payload);
|
||||
const endpoint = subscription.endpoint;
|
||||
|
||||
const vapidHeaders = await this.vapidService.createVapidAuthHeader(endpoint, 12 * 60 * 60, 'mailto:example@example.com');
|
||||
@@ -86,12 +85,12 @@ export class NotificationService {
|
||||
'auth': auth
|
||||
};
|
||||
|
||||
const vapidKeys: VapidKeys = this.vapidService.getVapidKeys()[0];
|
||||
return http_ece.encrypt(payload, {
|
||||
'salt': this.generateSalt(),
|
||||
'dh': this.vapidKeys.publicKey,
|
||||
'dh': vapidKeys.publicKey,
|
||||
'keyid': 'p256dh',
|
||||
'contentEncoding': 'aes128gcm'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { DBService } from './db.js';
|
||||
import { Subscription } from './Subscription.js';
|
||||
|
||||
export interface Subscription {
|
||||
export interface SubscriptionData {
|
||||
endpoint: string;
|
||||
keys: {
|
||||
p256dh: string;
|
||||
@@ -15,7 +16,7 @@ export class SubscriptionService {
|
||||
this.dbService = new DBService();
|
||||
}
|
||||
|
||||
async addSubscription(subscription: Subscription): Promise<void> {
|
||||
async addSubscription(subscription: SubscriptionData): Promise<void> {
|
||||
await this.dbService.saveSubscription(
|
||||
subscription.endpoint,
|
||||
subscription.keys.p256dh,
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
import { Database } from 'sqlite3';
|
||||
|
||||
import jwt from 'jsonwebtoken';
|
||||
import crypto from 'crypto';
|
||||
import { DBService } from './db.js';
|
||||
import { VapidKeys } from './VapidKeys.js';
|
||||
|
||||
export interface VapidKeys {
|
||||
export interface VapidKeyData {
|
||||
publicKey: string;
|
||||
privateKey: string;
|
||||
}
|
||||
|
||||
export class VapidService {
|
||||
private db: Database;
|
||||
private dbService: DBService;
|
||||
|
||||
constructor() {
|
||||
this.db = new Database('vapidKeys.db');
|
||||
this.initializeDatabase();
|
||||
this.dbService = new DBService();
|
||||
const keys = this.generateVAPIDKeys();
|
||||
this.addVapidKeys(keys);
|
||||
}
|
||||
|
||||
private initializeDatabase(): void {
|
||||
this.db.run(`CREATE TABLE IF NOT EXISTS vapid (id INTEGER PRIMARY KEY, publicKey TEXT, privateKey TEXT)`);
|
||||
}
|
||||
|
||||
private generateVAPIDKeys(): VapidKeys {
|
||||
private generateVAPIDKeys(): VapidKeyData {
|
||||
const ecdh = crypto.createECDH('prime256v1');
|
||||
ecdh.generateKeys();
|
||||
|
||||
@@ -29,26 +28,19 @@ export class VapidService {
|
||||
};
|
||||
}
|
||||
|
||||
async getVapidKeys(): Promise<VapidKeys> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.get('SELECT publicKey, privateKey FROM vapid WHERE id = ?', [1], (err, row: VapidKeys) => {
|
||||
if (err) reject(err);
|
||||
async addVapidKeys(vapidkeys: VapidKeyData) {
|
||||
const keys = await this.getVapidKeys();
|
||||
if (keys.length == 0) {
|
||||
this.dbService.saveVapidKeys(vapidkeys.publicKey, vapidkeys.privateKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (row) {
|
||||
resolve({ publicKey: row.publicKey, privateKey: row.privateKey });
|
||||
} else {
|
||||
const keys = this.generateVAPIDKeys();
|
||||
this.db.run('INSERT INTO vapid (publicKey, privateKey) VALUES (?, ?)', [keys.publicKey, keys.privateKey], (err) => {
|
||||
if (err) reject(err);
|
||||
resolve(keys);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
async getVapidKeys(): Promise<VapidKeys[]> {
|
||||
return this.dbService.getVapidKeys();
|
||||
}
|
||||
|
||||
async createVapidAuthHeader(endpoint: string, expiration: number, subject: string): Promise<{ 'Authorization': string, 'Crypto-Key': string }> {
|
||||
const { publicKey, privateKey } = await this.getVapidKeys();
|
||||
const { publicKey, privateKey } = await this.getVapidKeys()[0];
|
||||
|
||||
const jwtInfo = {
|
||||
aud: new URL(endpoint).origin,
|
||||
|
||||
@@ -8,8 +8,9 @@ class WorkerThread {
|
||||
|
||||
constructor(interval: number) {
|
||||
this.interval = interval;
|
||||
|
||||
this.notificationService = new NotificationService();
|
||||
this.message.title = "Check the app.";
|
||||
this.message = { "title": "Check the app." };
|
||||
|
||||
this.startPeriodicTask();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user