From d839c274b17ef803e7c4dd03d71969c5bd395f15 Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Thu, 2 Jan 2025 02:45:40 +0100 Subject: [PATCH] feat(api): :sparkles: Finish push notification delivery --- .github/config.workflow.toml | 14 +++ CHANGELOG.md | 32 ++++++- bun.lockb | Bin 398336 -> 403520 bytes classes/database/pushsubscription.ts | 25 ++++- classes/database/user.ts | 33 ++++++- classes/queues/push.ts | 18 ++++ classes/workers/push.ts | 128 +++++++++++++++++++++++++ config/config.example.toml | 27 +++++- config/config.schema.json | 98 +++++++++++++++++++ package.json | 2 + packages/config-manager/config.type.ts | 49 ++++++++++ utils/init.ts | 40 ++++++++ 12 files changed, 457 insertions(+), 9 deletions(-) create mode 100644 classes/queues/push.ts create mode 100644 classes/workers/push.ts diff --git a/.github/config.workflow.toml b/.github/config.workflow.toml index 267997f6..7d048662 100644 --- a/.github/config.workflow.toml +++ b/.github/config.workflow.toml @@ -189,6 +189,20 @@ expiration = 300 # 5 minutes # Leave this empty to generate a new key key = "YBpAV0KZOeM/MZ4kOb2E9moH9gCUr00Co9V7ncGRJ3wbd/a9tLDKKFdI0BtOcnlpfx0ZBh0+w3WSvsl0TsesTg==" +[notifications] + +[notifications.push] +# Whether to enable push notifications +enabled = true + +[notifications.push.vapid] +# VAPID keys for push notifications +# Run Versia Server with those values missing to generate new keys +public = "BBanhyj2_xWwbTsWld3T49VcAoKZHrVJTzF1f6Av2JwQY_wUi3CF9vZ0WeEcACRj6EEqQ7N35CkUh5epF7n4P_s" +private = "Eujaz7NsF0rKZOVrAFL7mMpFdl96f591ERsRn81unq0" +# Optional +# subject = "mailto:joe@example.com" + [defaults] # Default visibility for new notes visibility = "public" diff --git a/CHANGELOG.md b/CHANGELOG.md index ff23fd09..63202abf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,21 +19,49 @@ Versia Server `0.8.0` is fully backwards compatible with `0.7.0`. ## New Configuration Options ```toml +[notifications] + +[notifications.push] +# Whether to enable push notifications +enabled = true + +[notifications.push.vapid] +# VAPID keys for push notifications +# Run Versia Server with those values missing to generate new keys +public = "" +private = "" +# Optional +# subject = "mailto:joe@example.com" + [queues] -# Control the delivery queue (for outbound federation) +# Controls the delivery queue (for outbound federation) [queues.delivery] # Time in seconds to remove completed jobs remove_on_complete = 31536000 # Time in seconds to remove failed jobs remove_on_failure = 31536000 -# Control the inbox processing queue (for inbound federation) +# Controls the inbox processing queue (for inbound federation) [queues.inbox] # Time in seconds to remove completed jobs remove_on_complete = 31536000 # Time in seconds to remove failed jobs remove_on_failure = 31536000 +# Controls the fetch queue (for remote data refreshes) +[queues.fetch] +# Time in seconds to remove completed jobs +remove_on_complete = 31536000 +# Time in seconds to remove failed jobs +remove_on_failure = 31536000 + +# Controls the push queue (for push notification delivery) +[queues.push] +# Time in seconds to remove completed jobs +remove_on_complete = 31536000 +# Time in seconds to remove failed jobs +remove_on_failure = 31536000 + [validation] max_emoji_size = 1000000 max_emoji_shortcode_size = 100 diff --git a/bun.lockb b/bun.lockb index e1af2de5f6b536e6f9aaa84ae88eebf961be662b..c75e1f57c8350b1f4e6cd731889f38ec0e2b754f 100755 GIT binary patch delta 82813 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 delta 80027 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≤60?Gw_!%p1>st&xUDS%jsYM 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 MtE`$|DCLU(0VtSSumAu6 diff --git a/classes/database/pushsubscription.ts b/classes/database/pushsubscription.ts index 107e8605..efdc3219 100644 --- a/classes/database/pushsubscription.ts +++ b/classes/database/pushsubscription.ts @@ -3,8 +3,8 @@ import type { Alerts, PushSubscription as ApiPushSubscription, } from "@versia/client/types"; -import { type Token, db } from "@versia/kit/db"; -import { PushSubscriptions } from "@versia/kit/tables"; +import { type Token, type User, db } from "@versia/kit/db"; +import { PushSubscriptions, Users } from "@versia/kit/tables"; import { type InferInsertModel, type InferSelectModel, @@ -136,6 +136,27 @@ export class PushSubscription extends BaseInterface< ); } + public static async manyFromUser( + user: User, + limit?: number, + offset?: number, + ): Promise { + const found = await db.query.PushSubscriptions.findMany({ + where: (): SQL => eq(Users.id, user.id), + limit, + offset, + with: { + token: { + with: { + user: true, + }, + }, + }, + }); + + return found.map((s) => new PushSubscription(s)); + } + public static async fromSql( sql: SQL | undefined, orderBy: SQL | undefined = desc(PushSubscriptions.id), diff --git a/classes/database/user.ts b/classes/database/user.ts index 6bcac35f..882a4b72 100644 --- a/classes/database/user.ts +++ b/classes/database/user.ts @@ -21,7 +21,7 @@ import type { FollowReject as VersiaFollowReject, User as VersiaUser, } from "@versia/federation/types"; -import { Notification, db } from "@versia/kit/db"; +import { Notification, PushSubscription, db } from "@versia/kit/db"; import { EmojiToUser, Likes, @@ -54,6 +54,7 @@ import { searchManager } from "~/classes/search/search-manager"; import { type Config, config } from "~/packages/config-manager"; import type { KnownEntity } from "~/types/api.ts"; import { DeliveryJobType, deliveryQueue } from "../queues/delivery.ts"; +import { PushJobType, pushQueue } from "../queues/push.ts"; import { BaseInterface } from "./base.ts"; import { Emoji } from "./emoji.ts"; import { Instance } from "./instance.ts"; @@ -572,12 +573,40 @@ export class User extends BaseInterface { relatedUser: User, note?: Note, ): Promise { - await Notification.insert({ + const notification = await Notification.insert({ accountId: relatedUser.id, type, notifiedId: this.id, noteId: note?.id ?? null, }); + + // Also do push notifications + if (config.notifications.push.enabled) { + await this.notifyPush(notification.id, type, relatedUser, note); + } + } + + private async notifyPush( + notificationId: string, + type: "mention" | "follow_request" | "follow" | "favourite" | "reblog", + relatedUser: User, + note?: Note, + ): Promise { + // Fetch all push subscriptions + const ps = await PushSubscription.manyFromUser(this); + + pushQueue.addBulk( + ps.map((p) => ({ + data: { + psId: p.id, + type, + relatedUserId: relatedUser.id, + noteId: note?.id, + notificationId, + }, + name: PushJobType.Notify, + })), + ); } public async clearAllNotifications(): Promise { diff --git a/classes/queues/push.ts b/classes/queues/push.ts new file mode 100644 index 00000000..cd804c13 --- /dev/null +++ b/classes/queues/push.ts @@ -0,0 +1,18 @@ +import { Queue } from "bullmq"; +import { connection } from "~/utils/redis.ts"; + +export enum PushJobType { + Notify = "notify", +} + +export type PushJobData = { + psId: string; + type: string; + relatedUserId: string; + noteId?: string; + notificationId: string; +}; + +export const pushQueue = new Queue("push", { + connection, +}); diff --git a/classes/workers/push.ts b/classes/workers/push.ts new file mode 100644 index 00000000..cc50721b --- /dev/null +++ b/classes/workers/push.ts @@ -0,0 +1,128 @@ +import { Note, PushSubscription, Token, User } from "@versia/kit/db"; +import { Worker } from "bullmq"; +import { htmlToText } from "html-to-text"; +import { sendNotification } from "web-push"; +import { config } from "~/packages/config-manager"; +import { connection } from "~/utils/redis.ts"; +import { + type PushJobData, + type PushJobType, + pushQueue, +} from "../queues/push.ts"; + +export const getPushWorker = (): Worker => + new Worker( + pushQueue.name, + async (job) => { + const { + data: { psId, relatedUserId, type, noteId, notificationId }, + } = job; + + const ps = await PushSubscription.fromId(psId); + + if (!ps) { + throw new Error( + `Could not resolve push subscription ID ${psId}`, + ); + } + + const token = await Token.fromId(ps.data.tokenId); + + if (!token) { + throw new Error( + `Could not resolve token ID ${ps.data.tokenId}`, + ); + } + + const relatedUser = await User.fromId(relatedUserId); + + if (!relatedUser) { + throw new Error( + `Could not resolve related user ID ${relatedUserId}`, + ); + } + + const note = noteId ? await Note.fromId(noteId) : null; + + const truncate = (str: string, len: number): string => { + if (str.length <= len) { + return str; + } + + return `${str.slice(0, len)}...`; + }; + + const name = truncate( + relatedUser.data.displayName || relatedUser.data.username, + 50, + ); + + let title = name; + + switch (type) { + case "mention": + title = `${name} mentioned you`; + break; + case "reply": + title = `${name} replied to you`; + break; + case "like": + title = `${name} liked your note`; + break; + case "reblog": + title = `${name} reblogged your note`; + break; + case "follow": + title = `${name} followed you`; + break; + case "follow_request": + title = `${name} requested to follow you`; + break; + case "poll": + title = "Poll ended"; + break; + } + + const body = note + ? htmlToText(note.data.spoilerText || note.data.content) + : htmlToText(relatedUser.data.note); + + sendNotification( + { + endpoint: ps.data.endpoint, + keys: { + auth: ps.data.authSecret, + p256dh: ps.data.publicKey, + }, + }, + JSON.stringify({ + access_token: token.data.accessToken, + // FIXME + preferred_locale: "en-US", + notification_id: notificationId, + notification_type: type, + icon: relatedUser.getAvatarUrl(config), + title, + body: truncate(body, 140), + }), + { + vapidDetails: { + subject: + config.notifications.push.vapid.subject || + config.http.base_url, + privateKey: config.notifications.push.vapid.private, + publicKey: config.notifications.push.vapid.public, + }, + }, + ); + }, + { + connection, + removeOnComplete: { + age: config.queues.push.remove_on_complete, + }, + removeOnFail: { + age: config.queues.push.remove_on_failure, + }, + }, + ); diff --git a/config/config.example.toml b/config/config.example.toml index 3809d3f2..2e06a5a4 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -247,6 +247,20 @@ expiration = 300 # 5 minutes # Leave this empty to generate a new key key = "" +[notifications] + +[notifications.push] +# Whether to enable push notifications +enabled = true + +[notifications.push.vapid] +# VAPID keys for push notifications +# Run Versia Server with those values missing to generate new keys +public = "" +private = "" +# Optional +# subject = "mailto:joe@example.com" + [defaults] # Default visibility for new notes # Can be public, unlisted, private or direct @@ -262,27 +276,34 @@ language = "en" placeholder_style = "thumbs" [queues] -# Control the delivery queue (for outbound federation) +# Controls the delivery queue (for outbound federation) [queues.delivery] # Time in seconds to remove completed jobs remove_on_complete = 31536000 # Time in seconds to remove failed jobs remove_on_failure = 31536000 -# Control the inbox processing queue (for inbound federation) +# Controls the inbox processing queue (for inbound federation) [queues.inbox] # Time in seconds to remove completed jobs remove_on_complete = 31536000 # Time in seconds to remove failed jobs remove_on_failure = 31536000 -# Control the fetch queue (for remote data refreshes) +# Controls the fetch queue (for remote data refreshes) [queues.fetch] # Time in seconds to remove completed jobs remove_on_complete = 31536000 # Time in seconds to remove failed jobs remove_on_failure = 31536000 +# Controls the push queue (for push notification delivery) +[queues.push] +# Time in seconds to remove completed jobs +remove_on_complete = 31536000 +# Time in seconds to remove failed jobs +remove_on_failure = 31536000 + [federation] # This is a list of domain names, such as "mastodon.social" or "pleroma.site" # These changes will not retroactively apply to existing data before they were changed diff --git a/config/config.schema.json b/config/config.schema.json index 41683991..031508ac 100644 --- a/config/config.schema.json +++ b/config/config.schema.json @@ -3126,6 +3126,49 @@ } } }, + "notifications": { + "type": "object", + "properties": { + "push": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "default": true + }, + "vapid": { + "type": "object", + "properties": { + "public": { + "type": "string" + }, + "private": { + "type": "string" + }, + "subject": { + "type": "string" + } + }, + "required": ["public", "private"], + "additionalProperties": false, + "default": { + "public": "", + "private": "" + } + } + }, + "additionalProperties": false, + "default": { + "enabled": true, + "vapid": { + "public": "", + "private": "" + } + } + } + }, + "additionalProperties": false + }, "defaults": { "type": "object", "properties": { @@ -3364,6 +3407,24 @@ "remove_on_complete": 31536000, "remove_on_failure": 31536000 } + }, + "push": { + "type": "object", + "properties": { + "remove_on_complete": { + "type": "integer", + "default": 31536000 + }, + "remove_on_failure": { + "type": "integer", + "default": 31536000 + } + }, + "additionalProperties": false, + "default": { + "remove_on_complete": 31536000, + "remove_on_failure": 31536000 + } } }, "additionalProperties": false, @@ -3379,6 +3440,10 @@ "fetch": { "remove_on_complete": 31536000, "remove_on_failure": 31536000 + }, + "push": { + "remove_on_complete": 31536000, + "remove_on_failure": 31536000 } } }, @@ -3482,6 +3547,9 @@ "emojis", "read:emoji", "owner:emoji", + "read:reaction", + "reactions", + "owner:reaction", "media", "owner:media", "blocks", @@ -3501,6 +3569,7 @@ "owner:follow", "owner:app", "search", + "push_notifications", "public_timelines", "private_timelines", "ignore_rate_limits", @@ -3522,6 +3591,8 @@ "owner:boost", "read:account", "owner:emoji", + "read:reaction", + "owner:reaction", "read:emoji", "owner:media", "owner:block", @@ -3533,6 +3604,7 @@ "owner:follow", "owner:app", "search", + "push_notifications", "public_timelines", "private_timelines", "oauth" @@ -3559,6 +3631,9 @@ "emojis", "read:emoji", "owner:emoji", + "read:reaction", + "reactions", + "owner:reaction", "media", "owner:media", "blocks", @@ -3578,6 +3653,7 @@ "owner:follow", "owner:app", "search", + "push_notifications", "public_timelines", "private_timelines", "ignore_rate_limits", @@ -3599,6 +3675,8 @@ "owner:boost", "read:account", "owner:emoji", + "read:reaction", + "owner:reaction", "read:emoji", "owner:media", "owner:block", @@ -3610,6 +3688,7 @@ "owner:follow", "owner:app", "search", + "push_notifications", "public_timelines", "private_timelines", "oauth" @@ -3636,6 +3715,9 @@ "emojis", "read:emoji", "owner:emoji", + "read:reaction", + "reactions", + "owner:reaction", "media", "owner:media", "blocks", @@ -3655,6 +3737,7 @@ "owner:follow", "owner:app", "search", + "push_notifications", "public_timelines", "private_timelines", "ignore_rate_limits", @@ -3676,6 +3759,8 @@ "owner:boost", "read:account", "owner:emoji", + "read:reaction", + "owner:reaction", "read:emoji", "owner:media", "owner:block", @@ -3687,6 +3772,7 @@ "owner:follow", "owner:app", "search", + "push_notifications", "public_timelines", "private_timelines", "oauth", @@ -3695,6 +3781,7 @@ "likes", "boosts", "emojis", + "reactions", "media", "blocks", "filters", @@ -3725,6 +3812,8 @@ "owner:boost", "read:account", "owner:emoji", + "read:reaction", + "owner:reaction", "read:emoji", "owner:media", "owner:block", @@ -3736,6 +3825,7 @@ "owner:follow", "owner:app", "search", + "push_notifications", "public_timelines", "private_timelines", "oauth" @@ -3751,6 +3841,8 @@ "owner:boost", "read:account", "owner:emoji", + "read:reaction", + "owner:reaction", "read:emoji", "owner:media", "owner:block", @@ -3762,6 +3854,7 @@ "owner:follow", "owner:app", "search", + "push_notifications", "public_timelines", "private_timelines", "oauth" @@ -3777,6 +3870,8 @@ "owner:boost", "read:account", "owner:emoji", + "read:reaction", + "owner:reaction", "read:emoji", "owner:media", "owner:block", @@ -3788,6 +3883,7 @@ "owner:follow", "owner:app", "search", + "push_notifications", "public_timelines", "private_timelines", "oauth", @@ -3796,6 +3892,7 @@ "likes", "boosts", "emojis", + "reactions", "media", "blocks", "filters", @@ -4066,6 +4163,7 @@ "signups", "http", "smtp", + "notifications", "filters", "ratelimits" ], diff --git a/package.json b/package.json index c56be7e3..5479594f 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "@types/mime-types": "^2.1.4", "@types/pg": "^8.11.10", "@types/qs": "^6.9.17", + "@types/web-push": "^3.6.4", "drizzle-kit": "^0.30.1", "markdown-it-image-figures": "^2.1.1", "markdown-it-mathjax3": "^4.3.2", @@ -163,6 +164,7 @@ "table": "^6.9.0", "unzipit": "^1.4.3", "uqr": "^0.1.2", + "web-push": "^3.6.7", "xss": "^1.0.15", "zod": "^3.24.1", "zod-validation-error": "^3.4.0" diff --git a/packages/config-manager/config.type.ts b/packages/config-manager/config.type.ts index cd8016ae..bd346c50 100644 --- a/packages/config-manager/config.type.ts +++ b/packages/config-manager/config.type.ts @@ -454,6 +454,33 @@ export const configValidator = z key: "", }, }), + notifications: z + .object({ + push: z + .object({ + enabled: z.boolean().default(true), + vapid: z + .object({ + public: z.string(), + private: z.string(), + subject: z.string().optional(), + }) + .strict() + .default({ + public: "", + private: "", + }), + }) + .strict() + .default({ + enabled: true, + vapid: { + public: "", + private: "", + }, + }), + }) + .strict(), defaults: z .object({ visibility: z.string().default("public"), @@ -585,6 +612,24 @@ export const configValidator = z remove_on_complete: 60 * 60 * 24 * 365, remove_on_failure: 60 * 60 * 24 * 365, }), + push: z + .object({ + remove_on_complete: z + .number() + .int() + // 1 year + .default(60 * 60 * 24 * 365), + remove_on_failure: z + .number() + .int() + // 1 year + .default(60 * 60 * 24 * 365), + }) + .strict() + .default({ + remove_on_complete: 60 * 60 * 24 * 365, + remove_on_failure: 60 * 60 * 24 * 365, + }), }) .strict() .default({ @@ -600,6 +645,10 @@ export const configValidator = z remove_on_complete: 60 * 60 * 24 * 365, remove_on_failure: 60 * 60 * 24 * 365, }, + push: { + remove_on_complete: 60 * 60 * 24 * 365, + remove_on_failure: 60 * 60 * 24 * 365, + }, }), instance: z .object({ diff --git a/utils/init.ts b/utils/init.ts index ff7ffacb..a3cc2198 100644 --- a/utils/init.ts +++ b/utils/init.ts @@ -1,6 +1,7 @@ import { getLogger } from "@logtape/logtape"; import { User } from "@versia/kit/db"; import chalk from "chalk"; +import { generateVAPIDKeys } from "web-push"; import type { Config } from "~/packages/config-manager"; export const checkConfig = async (config: Config): Promise => { @@ -9,6 +10,8 @@ export const checkConfig = async (config: Config): Promise => { await checkHttpProxyConfig(config); await checkChallengeConfig(config); + + await checkVapidConfig(config); }; const checkHttpProxyConfig = async (config: Config): Promise => { @@ -110,3 +113,40 @@ const checkFederationConfig = async (config: Config): Promise => { ); } }; + +const checkVapidConfig = async (config: Config): Promise => { + const logger = getLogger("server"); + + if ( + config.notifications.push.enabled && + !( + config.notifications.push.vapid.public || + config.notifications.push.vapid.private + ) + ) { + logger.fatal`The VAPID keys are not set in the config, but push notifications are enabled.`; + logger.fatal`Below are generated keys for you to copy in the config at notifications.push.vapid`; + + const { privateKey, publicKey } = await generateVAPIDKeys(); + + logger.fatal`Generated public key: ${chalk.gray(publicKey)}`; + logger.fatal`Generated private key: ${chalk.gray(privateKey)}`; + + // Hang until Ctrl+C is pressed + await Bun.sleep(Number.POSITIVE_INFINITY); + } + + // These use a format I don't understand, so I'm just going to check the length + const validateKey = (key: string): boolean => key.length > 10; + + if ( + !( + validateKey(config.notifications.push.vapid.public) && + validateKey(config.notifications.push.vapid.private) + ) + ) { + throw new Error( + "The VAPID keys could not be imported! You may generate new ones by removing the old ones from the config and restarting the server.", + ); + } +};