From 7fbda4a56bfb592c8852177bbd919f5270b68a01 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 15 Oct 2024 17:13:39 -0500 Subject: [PATCH] Move push notification rendering to its own view --- src/pipeline.ts | 24 ++++--------- src/views/mastodon/notifications.ts | 12 +++---- src/views/mastodon/push.ts | 52 +++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 23 deletions(-) create mode 100644 src/views/mastodon/push.ts diff --git a/src/pipeline.ts b/src/pipeline.ts index b965f598..57a5f76a 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -2,7 +2,6 @@ import { NKinds, NostrEvent, NSchema as n } from '@nostrify/nostrify'; import { Stickynotes } from '@soapbox/stickynotes'; import { Kysely, sql } from 'kysely'; import { LRUCache } from 'lru-cache'; -import { nip19 } from 'nostr-tools'; import { z } from 'zod'; import { Conf } from '@/config.ts'; @@ -14,7 +13,6 @@ import { RelayError } from '@/RelayError.ts'; import { AdminSigner } from '@/signers/AdminSigner.ts'; import { hydrateEvents } from '@/storages/hydrate.ts'; import { Storages } from '@/storages.ts'; -import { MastodonPush } from '@/types/MastodonPush.ts'; import { eventAge, parseNip05, Time } from '@/utils.ts'; import { getAmount } from '@/utils/bolt11.ts'; import { detectLanguage } from '@/utils/language.ts'; @@ -22,7 +20,7 @@ import { nip05Cache } from '@/utils/nip05.ts'; import { purifyEvent } from '@/utils/purify.ts'; import { updateStats } from '@/utils/stats.ts'; import { getTagSet } from '@/utils/tags.ts'; -import { renderNotification } from '@/views/mastodon/notifications.ts'; +import { renderWebPushNotification } from '@/views/mastodon/push.ts'; import { policyWorker } from '@/workers/policy.ts'; import { verifyEventWorker } from '@/workers/verify.ts'; @@ -257,12 +255,14 @@ async function webPush(event: NostrEvent): Promise { .execute(); for (const row of rows) { - if (row.pubkey === event.pubkey) { + const viewerPubkey = row.pubkey; + + if (viewerPubkey === event.pubkey) { continue; // Don't notify authors about their own events. } - const notification = await renderNotification(event, { viewerPubkey: row.pubkey }); - if (!notification) { + const message = await renderWebPushNotification(event, viewerPubkey); + if (!message) { continue; } @@ -274,18 +274,8 @@ async function webPush(event: NostrEvent): Promise { }, }; - const message: MastodonPush = { - notification_id: notification.id, - notification_type: notification.type, - access_token: nip19.npubEncode(row.pubkey), - preferred_locale: 'en', - title: notification.account.display_name || notification.account.username, - icon: notification.account.avatar_static, - body: event.content, - }; - await DittoPush.push(subscription, message); - webPushNotificationsCounter.inc({ type: notification.type }); + webPushNotificationsCounter.inc({ type: message.notification_type }); } } diff --git a/src/views/mastodon/notifications.ts b/src/views/mastodon/notifications.ts index 973a7a6d..4cf6eb5c 100644 --- a/src/views/mastodon/notifications.ts +++ b/src/views/mastodon/notifications.ts @@ -44,7 +44,7 @@ async function renderMention(event: DittoEvent, opts: RenderNotificationOpts) { return { id: notificationId(event), - type: 'mention', + type: 'mention' as const, created_at: nostrDate(event.created_at).toISOString(), account: status.account, status: status, @@ -59,7 +59,7 @@ async function renderReblog(event: DittoEvent, opts: RenderNotificationOpts) { return { id: notificationId(event), - type: 'reblog', + type: 'reblog' as const, created_at: nostrDate(event.created_at).toISOString(), account, status, @@ -74,7 +74,7 @@ async function renderFavourite(event: DittoEvent, opts: RenderNotificationOpts) return { id: notificationId(event), - type: 'favourite', + type: 'favourite' as const, created_at: nostrDate(event.created_at).toISOString(), account, status, @@ -89,7 +89,7 @@ async function renderReaction(event: DittoEvent, opts: RenderNotificationOpts) { return { id: notificationId(event), - type: 'pleroma:emoji_reaction', + type: 'pleroma:emoji_reaction' as const, emoji: event.content, emoji_url: event.tags.find(([name, value]) => name === 'emoji' && `:${value}:` === event.content)?.[2], created_at: nostrDate(event.created_at).toISOString(), @@ -106,7 +106,7 @@ async function renderNameGrant(event: DittoEvent) { return { id: notificationId(event), - type: 'ditto:name_grant', + type: 'ditto:name_grant' as const, name: d, created_at: nostrDate(event.created_at).toISOString(), account, @@ -125,7 +125,7 @@ async function renderZap(event: DittoEvent, opts: RenderNotificationOpts) { return { id: notificationId(event), - type: 'ditto:zap', + type: 'ditto:zap' as const, amount: zap_amount, message: zap_message, created_at: nostrDate(event.created_at).toISOString(), diff --git a/src/views/mastodon/push.ts b/src/views/mastodon/push.ts new file mode 100644 index 00000000..9c5572e1 --- /dev/null +++ b/src/views/mastodon/push.ts @@ -0,0 +1,52 @@ +import type { NostrEvent } from '@nostrify/nostrify'; +import { nip19 } from 'nostr-tools'; + +import { MastodonPush } from '@/types/MastodonPush.ts'; +import { renderNotification } from '@/views/mastodon/notifications.ts'; + +/** + * Render a web push notification for the viewer. + * Unlike other views, only one will be rendered at a time, so making use of async calls is okay. + */ +export async function renderWebPushNotification( + event: NostrEvent, + viewerPubkey: string, +): Promise { + const notification = await renderNotification(event, { viewerPubkey }); + if (!notification) { + return; + } + + return { + notification_id: notification.id, + notification_type: notification.type, + access_token: nip19.npubEncode(viewerPubkey), + preferred_locale: 'en', + title: renderTitle(notification), + icon: notification.account.avatar_static, + body: event.content, + }; +} + +type MastodonNotification = NonNullable>>; + +function renderTitle(notification: MastodonNotification): string { + const { account } = notification; + + switch (notification.type) { + case 'ditto:name_grant': + return `You were granted the name ${notification.name}`; + case 'ditto:zap': + return `${account.display_name} zapped you ${notification.amount} sats`; + case 'pleroma:emoji_reaction': + return `${account.display_name} reacted to your post`; + case 'favourite': + return `${account.display_name} liked your post`; + case 'mention': + return `${account.display_name} mentioned you`; + case 'reblog': + return `${account.display_name} reposted your post`; + default: + return account.display_name || account.username; + } +}