sure-backend/src/index.ts

135 lines
3.6 KiB
TypeScript

// src/index.ts
import express, { Express } from 'express';
import dotenv from 'dotenv';
import { json } from 'body-parser';
import webpush from 'web-push';
import crypto from 'crypto';
import sqlite3 from 'sqlite3';
import cors from 'cors';
dotenv.config();
interface Subscription {
id: string;
endpoint: string;
keys: string;
}
const app: Express = express();
const port = process.env.PORT || 3000;
let secret = process.env.SECRET;
let vapidKeys = {
publicKey: process.env.VAPID_PUBLIC_KEY,
privateKey: process.env.VAPID_PRIVATE_KEY,
};
if (!vapidKeys.publicKey || !vapidKeys.privateKey || secret === undefined) {
console.log(
'SECRET, VAPID_PUBLIC_KEY, or VAPID_PRIVATE_KEY is not defined. Please run `npx run generate-keys` to generate VAPID keys, server secret, and store them in a `.env` file at the root of the project.',
);
process.exit(1);
}
webpush.setVapidDetails(
`mailto:${process.env.VAPID_EMAIL}`,
vapidKeys.publicKey,
vapidKeys.privateKey,
);
const db = new sqlite3.Database('subscriptions.db');
db.serialize(() => {
db.run(`
CREATE TABLE IF NOT EXISTS subscriptions (
id TEXT,
endpoint TEXT NOT NULL,
keys TEXT NOT NULL,
PRIMARY KEY (id, endpoint)
)
`);
});
function createHash(publicKey: string, secret: string): string {
const publicKeyBuffer = Buffer.from(publicKey, 'base64');
const hash = crypto.createHmac('sha256', secret);
hash.update(publicKeyBuffer);
return hash.digest('hex');
}
app.use(json());
app.use(
cors({
origin:
process.env.NODE_ENV === 'production' ? process.env.PROD_ORIGIN : '*',
}),
);
app.post('/api/subscribe', async (req, res) => {
let { subscription, publicKey } = req.body;
const id = await createHash(publicKey, secret!);
await db.run(
'INSERT OR REPLACE INTO subscriptions (id, endpoint, keys) VALUES (?, ?, ?)',
id,
subscription.endpoint,
JSON.stringify(subscription.keys),
);
res.status(201).json({});
});
app.get('/api/public-key', (req, res) => {
res.status(200).json({ publicKey: vapidKeys.publicKey });
});
app.post('/api/send-notification', async (req, res) => {
let { publicKey, url } = req.body;
const id = createHash(publicKey, secret!);
// get the subscription from the database
db.all(
'SELECT * FROM subscriptions WHERE id = ?',
id,
(err, subscriptions: Subscription[]) => {
if (err) {
console.error(err);
return res
.status(500)
.send({ error: 'Failed to retrieve subscriptions' });
}
if (subscriptions) {
const payload = JSON.stringify({
body: 'You have a new encrypted message! Click here to view it.',
data: {
url: url,
},
});
Promise.all(
subscriptions.map((subscription) => {
const keys = JSON.parse(subscription.keys);
return webpush.sendNotification(
{ endpoint: subscription.endpoint, keys },
payload,
);
}),
)
.then(() => res.status(200).send({ success: true }))
.catch((error) => {
console.error(error);
res.status(500).send({ error: 'Failed to send notification' });
});
} else {
res.status(404).send({ error: 'No subscriptions found for this id' });
}
},
);
});
app.listen(port, () => {
console.log(
`[server]: Server is running at ${
process.env.NODE_ENV ? process.env.PROD_ORIGIN : process.env.DEV_ORIGIN
}`,
);
});