Browse Source
- Add detailed file-level documentation with @fileoverview tags - Document all classes, methods, interfaces, and properties with JSDoc - Include author attribution (Matthew Raymer) and version information - Add rich logging with tagged console messages throughout codebase - Update all @since dates to reflect actual project timeline (2023-09-06) - Add current documentation update date (2025-07-23) to README - Document singleton patterns, async methods, and security considerations - Add parameter and return value documentation for all functions - Include TypeORM entity documentation with property descriptions - Enhance README with project intent and middleware architecture details Files updated: - src/main.ts: Server entry point and API route documentation - src/notificationService.ts: Push delivery and encryption documentation - src/subscriptionService.ts: Subscription management documentation - src/vapidService.ts: VAPID key generation and authentication docs - src/worker.ts: Background worker thread documentation - src/db.ts: Database service and TypeORM integration docs - src/Subscription.ts: Database entity documentation - src/VapidKeys.ts: VAPID keys entity documentation - README.md: Enhanced project documentation and timeline This commit significantly improves code maintainability and developer onboarding by providing comprehensive documentation for the entire push notification middleware codebase.master
9 changed files with 900 additions and 111 deletions
@ -1,39 +1,273 @@ |
|||
# Push Server for Time Safari |
|||
# Time Safari Push Notification Middleware Server |
|||
|
|||
A custom-built push notification middleware service designed specifically for the Time Safari application. This server acts as an intermediary layer between the Time Safari client applications and various push notification providers, providing a unified interface for managing push subscriptions and delivering notifications. |
|||
|
|||
## Setup Environment |
|||
## Project Intent |
|||
|
|||
The primary goal of this project was to build our own middleware service for push notifications rather than relying on third-party services. This approach provides several benefits: |
|||
|
|||
- **Complete Control**: Full ownership over the notification delivery pipeline |
|||
- **Customization**: Ability to implement Time Safari-specific notification logic |
|||
- **Privacy**: No dependency on external services that might collect user data |
|||
- **Cost Efficiency**: Eliminates per-notification costs from third-party providers |
|||
- **Reliability**: Direct control over uptime and performance |
|||
|
|||
## Architecture Overview |
|||
|
|||
The server is built using Node.js with TypeScript and follows a modular architecture: |
|||
|
|||
Tea isn't required but it's what we use to set up the dev environment. |
|||
``` |
|||
sh <(curl tea.xyz) -E sh |
|||
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ |
|||
│ Client Apps │───▶│ Push Server │───▶│ Push Providers │ |
|||
│ (Time Safari) │ │ (Middleware) │ │ (FCM, etc.) │ |
|||
└─────────────────┘ └──────────────────┘ └─────────────────┘ |
|||
│ |
|||
▼ |
|||
┌──────────────────┐ |
|||
│ SQLite DB │ |
|||
│ (Subscriptions) │ |
|||
└──────────────────┘ |
|||
``` |
|||
|
|||
#### Dependencies |
|||
### Core Components |
|||
|
|||
See https://tea.xyz |
|||
- **Subscription Management**: Handles client subscription registration and storage |
|||
- **VAPID Key Management**: Generates and manages VAPID keys for secure push delivery |
|||
- **Notification Service**: Encrypts and delivers push notifications to subscribed clients |
|||
- **Worker Thread**: Handles periodic tasks (currently sends daily notifications) |
|||
- **Database Layer**: SQLite-based storage for subscriptions and VAPID keys |
|||
|
|||
| Project | Version | |
|||
| ---------- | --------- | |
|||
| nodejs.org | ^16.0.0 | |
|||
| npmjs.com | ^8.0.0 | |
|||
## Features |
|||
|
|||
### 🔐 Secure Push Delivery |
|||
- VAPID (Voluntary Application Server Identification) key generation and management |
|||
- End-to-end encryption using AES-128-GCM |
|||
- JWT-based authentication headers |
|||
|
|||
## Install |
|||
``` |
|||
### 📱 Subscription Management |
|||
- Client subscription registration via `/subscribe` endpoint |
|||
- Subscription storage in SQLite database |
|||
- Support for subscription muting (planned) |
|||
- Subscription removal (planned) |
|||
|
|||
### ⚡ Real-time Notifications |
|||
- Broadcast notifications to all subscribed clients |
|||
- Individual client notification delivery |
|||
- Automatic daily notification scheduling via worker thread |
|||
|
|||
### 🛠️ Developer-Friendly |
|||
- TypeScript for type safety |
|||
- Comprehensive logging |
|||
- Modular service architecture |
|||
- Docker support for containerization |
|||
|
|||
## Technology Stack |
|||
|
|||
- **Runtime**: Node.js 18.12+ |
|||
- **Language**: TypeScript |
|||
- **Framework**: Express.js |
|||
- **Database**: SQLite with TypeORM |
|||
- **Encryption**: http_ece, elliptic, crypto |
|||
- **Authentication**: JWT (JSON Web Tokens) |
|||
- **Containerization**: Docker |
|||
|
|||
## Setup and Installation |
|||
|
|||
### Prerequisites |
|||
|
|||
- Node.js 18.12 or higher |
|||
- npm 8.0 or higher |
|||
|
|||
### Quick Start with Tea (Recommended) |
|||
|
|||
```bash |
|||
# Install Tea package manager |
|||
sh <(curl tea.xyz) -E sh |
|||
|
|||
# Install dependencies |
|||
npm install |
|||
``` |
|||
|
|||
# Build the project |
|||
npm run build |
|||
|
|||
## Run |
|||
# Start the server |
|||
npm run start |
|||
``` |
|||
|
|||
### Manual Setup |
|||
|
|||
```bash |
|||
# Clone the repository |
|||
git clone <repository-url> |
|||
cd pwa-push-server |
|||
|
|||
# Install dependencies |
|||
npm install |
|||
|
|||
# Build TypeScript |
|||
npm run build |
|||
|
|||
# Start the server |
|||
npm run start |
|||
``` |
|||
|
|||
The server will start on `http://localhost:3000` |
|||
|
|||
### Docker Setup |
|||
|
|||
```bash |
|||
# Build the Docker image |
|||
docker build -t timesafari-push-server . |
|||
|
|||
# Run the container |
|||
docker run -p 3000:3000 timesafari-push-server |
|||
``` |
|||
|
|||
## API Endpoints |
|||
|
|||
### POST `/subscribe` |
|||
Register a new push subscription. |
|||
|
|||
**Request Body:** |
|||
```json |
|||
{ |
|||
"endpoint": "https://fcm.googleapis.com/fcm/send/...", |
|||
"keys": { |
|||
"p256dh": "base64-encoded-public-key", |
|||
"auth": "base64-encoded-auth-secret" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
**Response:** `201 Created` |
|||
|
|||
### GET `/vapid` |
|||
Retrieve the public VAPID key for client-side subscription. |
|||
|
|||
**Response:** |
|||
```json |
|||
{ |
|||
"vapidKey": "base64-encoded-public-vapid-key" |
|||
} |
|||
``` |
|||
|
|||
### POST `/unsubscribe` (Planned) |
|||
Remove a push subscription. |
|||
|
|||
### POST `/mute` (Planned) |
|||
Toggle notification muting for a subscription. |
|||
|
|||
## Client Integration |
|||
|
|||
### 1. Request VAPID Key |
|||
```javascript |
|||
const response = await fetch('/vapid'); |
|||
const { vapidKey } = await response.json(); |
|||
``` |
|||
|
|||
### 2. Subscribe to Push Notifications |
|||
```javascript |
|||
const registration = await navigator.serviceWorker.ready; |
|||
const subscription = await registration.pushManager.subscribe({ |
|||
userVisibleOnly: true, |
|||
applicationServerKey: vapidKey |
|||
}); |
|||
|
|||
await fetch('/subscribe', { |
|||
method: 'POST', |
|||
headers: { 'Content-Type': 'application/json' }, |
|||
body: JSON.stringify(subscription) |
|||
}); |
|||
``` |
|||
|
|||
### 3. Handle Push Notifications |
|||
```javascript |
|||
// In your service worker |
|||
self.addEventListener('push', (event) => { |
|||
const data = event.data.json(); |
|||
const options = { |
|||
title: data.title, |
|||
body: data.body, |
|||
icon: '/icon.png', |
|||
badge: '/badge.png' |
|||
}; |
|||
|
|||
event.waitUntil( |
|||
self.registration.showNotification(data.title, options) |
|||
); |
|||
}); |
|||
``` |
|||
|
|||
## Development |
|||
|
|||
### Available Scripts |
|||
|
|||
- `npm run build` - Build the TypeScript project |
|||
- `npm run start` - Start the production server |
|||
- `npm run test` - Run tests with coverage |
|||
- `npm run lint` - Run ESLint |
|||
- `npm run prettier` - Format code with Prettier |
|||
|
|||
### Project Structure |
|||
|
|||
``` |
|||
src/ |
|||
├── main.ts # Server entry point and Express setup |
|||
├── notificationService.ts # Push notification delivery logic |
|||
├── subscriptionService.ts # Subscription management |
|||
├── vapidService.ts # VAPID key generation and management |
|||
├── db.ts # Database service and TypeORM setup |
|||
├── worker.ts # Background worker for periodic tasks |
|||
├── Subscription.ts # Subscription entity model |
|||
└── VapidKeys.ts # VAPID keys entity model |
|||
``` |
|||
|
|||
## Security Considerations |
|||
|
|||
- **VAPID Keys**: Automatically generated and securely stored |
|||
- **Encryption**: All push payloads are encrypted using AES-128-GCM |
|||
- **Authentication**: JWT-based VAPID authentication headers |
|||
- **Database**: SQLite with proper entity validation |
|||
- **Input Validation**: TypeScript provides compile-time type safety |
|||
|
|||
## Monitoring and Logging |
|||
|
|||
The server includes comprehensive logging for: |
|||
- Subscription events (add/remove) |
|||
- Notification delivery attempts |
|||
- VAPID key operations |
|||
- Database operations |
|||
- Worker thread activities |
|||
|
|||
## Future Enhancements |
|||
|
|||
- [ ] Subscription muting functionality |
|||
- [ ] Subscription removal endpoint |
|||
- [ ] Notification templates |
|||
- [ ] Delivery analytics and metrics |
|||
- [ ] Rate limiting and throttling |
|||
- [ ] Multi-tenant support |
|||
- [ ] Webhook support for external integrations |
|||
|
|||
## Contributing |
|||
|
|||
1. Fork the repository |
|||
2. Create a feature branch |
|||
3. Make your changes following the coding standards |
|||
4. Add tests for new functionality |
|||
5. Submit a pull request |
|||
|
|||
## License |
|||
|
|||
Apache-2.0 License - see [LICENSE](LICENSE) file for details. |
|||
|
|||
## Author |
|||
|
|||
**Matthew Raymer** - Lead Developer |
|||
|
|||
## Thanks |
|||
--- |
|||
|
|||
* [node-typescript-boilerplate][boilerplate] for project setup |
|||
*This middleware service was built specifically for Time Safari to provide reliable, secure, and cost-effective push notification delivery without dependency on third-party services.* |
|||
|
|||
[boilerplate]: https://github.com/jsynowiec/node-typescript-boilerplate |
|||
**Last Updated:** 2025-07-23 |
|||
**Project Started:** 2023-09-06 |
|||
|
@ -1,20 +1,43 @@ |
|||
// Subscription.ts
|
|||
/** |
|||
* @fileoverview Subscription entity for Time Safari push notifications |
|||
* |
|||
* This file defines the Subscription entity used by TypeORM for database persistence. |
|||
* It represents a push notification subscription with endpoint, encryption keys, |
|||
* and muting status. |
|||
* |
|||
* @author Matthew Raymer |
|||
* @version 1.0.0 |
|||
* @since 2023-09-06 |
|||
*/ |
|||
|
|||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; |
|||
|
|||
/** |
|||
* Entity class representing a push notification subscription |
|||
* |
|||
* @class Subscription |
|||
* @description TypeORM entity that maps to the subscriptions table in the database. |
|||
* Stores client subscription information including endpoint URLs and encryption keys. |
|||
*/ |
|||
@Entity() |
|||
export class Subscription { |
|||
/** Unique identifier for the subscription */ |
|||
@PrimaryGeneratedColumn() |
|||
id: number; |
|||
|
|||
/** The push service endpoint URL (e.g., FCM endpoint) */ |
|||
@Column() |
|||
endpoint: string; |
|||
|
|||
/** P-256 ECDH public key for message encryption */ |
|||
@Column() |
|||
keys_p256dh: string; |
|||
|
|||
/** Authentication secret for message integrity */ |
|||
@Column() |
|||
keys_auth: string; |
|||
|
|||
/** Flag indicating if notifications are muted for this subscription */ |
|||
@Column() |
|||
muted: boolean = false; |
|||
} |
|||
|
@ -1,14 +1,35 @@ |
|||
// VapidKeys.ts
|
|||
/** |
|||
* @fileoverview VAPID keys entity for Time Safari push notifications |
|||
* |
|||
* This file defines the VapidKeys entity used by TypeORM for database persistence. |
|||
* It represents a VAPID (Voluntary Application Server Identification) key pair |
|||
* used for authenticating push notification requests. |
|||
* |
|||
* @author Matthew Raymer |
|||
* @version 1.0.0 |
|||
* @since 2023-09-06 |
|||
*/ |
|||
|
|||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; |
|||
|
|||
/** |
|||
* Entity class representing a VAPID key pair |
|||
* |
|||
* @class VapidKeys |
|||
* @description TypeORM entity that maps to the vapid_keys table in the database. |
|||
* Stores VAPID public and private keys used for push notification authentication. |
|||
*/ |
|||
@Entity() |
|||
export class VapidKeys { |
|||
/** Unique identifier for the VAPID key pair */ |
|||
@PrimaryGeneratedColumn() |
|||
id: number; |
|||
|
|||
/** Base64-encoded public key for client-side subscription */ |
|||
@Column() |
|||
publicKey: string; |
|||
|
|||
/** Base64-encoded private key for server-side authentication */ |
|||
@Column() |
|||
privateKey: string; |
|||
} |
|||
|
@ -1,20 +1,62 @@ |
|||
/** |
|||
* @fileoverview Background worker thread for Time Safari push notifications |
|||
* |
|||
* This worker thread runs in the background and handles periodic tasks such as |
|||
* sending scheduled notifications. It communicates with the main thread via |
|||
* message passing to coordinate notification delivery. |
|||
* |
|||
* @author Matthew Raymer |
|||
* @version 1.0.0 |
|||
* @since 2023-09-06 |
|||
*/ |
|||
|
|||
import { parentPort } from 'worker_threads'; |
|||
|
|||
/** |
|||
* Worker thread class for handling periodic background tasks |
|||
* |
|||
* @class WorkerThread |
|||
* @description Manages periodic tasks that run in a separate thread to avoid |
|||
* blocking the main server thread. Currently handles daily notification scheduling. |
|||
*/ |
|||
class WorkerThread { |
|||
/** Interval in milliseconds between task executions */ |
|||
private interval: number; |
|||
|
|||
/** |
|||
* Creates a new worker thread instance |
|||
* |
|||
* @param {number} interval - The interval in milliseconds between task executions |
|||
* @description Initializes the worker thread and starts the periodic task scheduler |
|||
*/ |
|||
constructor(interval: number) { |
|||
this.interval = interval; |
|||
this.startPeriodicTask(); |
|||
console.log('[WORKER] Worker thread initialized with interval:', interval, 'ms'); |
|||
} |
|||
|
|||
/** |
|||
* Starts the periodic task scheduler |
|||
* |
|||
* @private |
|||
* @description Sets up an interval timer that sends messages to the main thread |
|||
* at the specified interval. Currently triggers daily notifications. |
|||
*/ |
|||
private startPeriodicTask(): void { |
|||
console.log('[WORKER] Starting periodic task scheduler'); |
|||
|
|||
setInterval(() => { |
|||
if (parentPort) { |
|||
parentPort.postMessage("send notifications") |
|||
console.log('[WORKER] Sending notification trigger to main thread'); |
|||
parentPort.postMessage("send notifications"); |
|||
} else { |
|||
console.error('[WORKER] Parent port not available'); |
|||
} |
|||
}, this.interval); |
|||
|
|||
console.log('[WORKER] Periodic task scheduler started successfully'); |
|||
} |
|||
} |
|||
|
|||
new WorkerThread(24*3600*1000); // pole once per day
|
|||
// Initialize the worker thread with a 24-hour interval (daily notifications)
|
|||
new WorkerThread(24 * 3600 * 1000); // Poll once per day
|
|||
|
Loading…
Reference in new issue