-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
123 lines (110 loc) · 3.62 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { Hono } from "@hono/hono";
import { serveStatic } from "@hono/hono/deno";
import { HTTPException } from "@hono/hono/http-exception";
import { openKv } from "./kv.ts";
import { composeNotification } from "./scripts/compose-notification.ts";
import { decryptMessage } from "./scripts/decrypt-message.ts";
import { isSubscription } from "./scripts/is-subscription.ts";
import { sendNotificationApns } from "./scripts/send-notification-apns.ts";
import { sendNotificationFcm } from "./scripts/send-notification-fcm.ts";
import { validateSubscription } from "./scripts/validate-subscription.ts";
import { validateVapid } from "./scripts/validate-vapid.ts";
import type { Subscription } from "./types.ts";
const app = new Hono();
export const kv = await openKv();
app.get("/", serveStatic({ path: "./index.html" }));
app.post("/subscriptions", async (c) => {
const params = await c.req.json();
if (params !== null && typeof params === "object") {
params.id ??= crypto.randomUUID();
}
if (
!isSubscription(params) || params.id.length < 36 ||
(params.fcmToken === undefined && params.apnsToken === undefined)
) {
throw new HTTPException(
400,
{ message: "The parameters are invalid." },
);
}
const subscription: Subscription = {
id: params.id,
fcmToken: params.fcmToken,
apnsToken: params.apnsToken,
auth: params.auth,
publicKey: params.publicKey,
privateKey: params.privateKey,
vapidKey: params.vapidKey,
};
try {
await validateSubscription(subscription);
} catch (e) {
throw new HTTPException(
400,
{ message: (e as Error).message },
);
}
const entry = await kv.get(["subscriptions", subscription.id]);
if (entry.value !== null) {
throw new HTTPException(
400,
{ message: "A subscription with the same id already exists." },
);
}
await kv.set(["subscriptions", subscription.id], subscription);
return c.json(subscription, 201);
});
app.post("/subscriptions/:id", async (c) => {
const id = c.req.param("id");
const { value: subscription } = await kv.get(["subscriptions", id]);
if (!isSubscription(subscription)) {
await kv.delete(["subscriptions", id]);
throw new HTTPException(410);
}
const header = c.req.header();
const authorization = header.authorization;
if (authorization === undefined || !authorization.startsWith("vapid")) {
throw new HTTPException(401);
}
let payload;
try {
payload = await validateVapid(authorization, subscription.vapidKey);
} catch (_) {
throw new HTTPException(403);
}
let host;
if (payload.sub && URL.canParse(payload.sub)) {
const url = new URL(payload.sub);
host = url.hostname;
}
const body = await c.req.arrayBuffer();
const message = await decryptMessage({
buffer: body,
auth: subscription.auth,
publicKey: subscription.publicKey,
privateKey: subscription.privateKey,
});
const notification = composeNotification(message, host);
try {
if (subscription.fcmToken !== undefined) {
await sendNotificationFcm(notification, subscription.fcmToken);
}
if (subscription.apnsToken !== undefined) {
await sendNotificationApns(notification, subscription.apnsToken);
}
return c.body(null, 204);
} catch (e) {
const status = (e as Response).status;
if (status === 401 || status === 403 || status === 404) {
await kv.delete(["subscriptions", id]);
throw new HTTPException(410);
}
throw new HTTPException();
}
});
app.delete("/subscriptions/:id", async (c) => {
const id = c.req.param("id");
await kv.delete(["subscriptions", id]);
return c.body(null, 204);
});
Deno.serve(app.fetch);