From 8823c0987db04e065caad3aa1787a41e7a0c66e3 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 8 Oct 2024 18:37:23 -0500 Subject: [PATCH] Actually push ?? --- deno.json | 1 + deno.lock | 37 +++++++++++++++++++++++++++++++ src/DittoPush.ts | 36 ++++++++++++++++++++++++++++++ src/config.ts | 4 ++++ src/pipeline.ts | 46 ++++++++++++++++++++++++++++++++++++--- src/types/MastodonPush.ts | 8 +++---- 6 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 src/DittoPush.ts diff --git a/deno.json b/deno.json index f97d4fa7..6c0fb961 100644 --- a/deno.json +++ b/deno.json @@ -40,6 +40,7 @@ "@hono/hono": "jsr:@hono/hono@^4.4.6", "@isaacs/ttlcache": "npm:@isaacs/ttlcache@^1.4.1", "@lambdalisue/async": "jsr:@lambdalisue/async@^2.1.1", + "@negrel/webpush": "jsr:@negrel/webpush@^0.3.0", "@noble/secp256k1": "npm:@noble/secp256k1@^2.0.0", "@nostrify/db": "jsr:@nostrify/db@^0.35.0", "@nostrify/nostrify": "jsr:@nostrify/nostrify@^0.36.0", diff --git a/deno.lock b/deno.lock index 84969bb2..446a4392 100644 --- a/deno.lock +++ b/deno.lock @@ -22,6 +22,9 @@ "jsr:@gleasonator/policy@0.7.1": "jsr:@gleasonator/policy@0.7.1", "jsr:@hono/hono@^4.4.6": "jsr:@hono/hono@4.6.2", "jsr:@lambdalisue/async@^2.1.1": "jsr:@lambdalisue/async@2.1.1", + "jsr:@negrel/http-ece@0.6.0": "jsr:@negrel/http-ece@0.6.0", + "jsr:@negrel/webpush": "jsr:@negrel/webpush@0.3.0", + "jsr:@negrel/webpush@^0.3.0": "jsr:@negrel/webpush@0.3.0", "jsr:@nostrify/db@^0.35.0": "jsr:@nostrify/db@0.35.0", "jsr:@nostrify/nostrify@^0.22.1": "jsr:@nostrify/nostrify@0.22.5", "jsr:@nostrify/nostrify@^0.22.4": "jsr:@nostrify/nostrify@0.22.4", @@ -44,6 +47,7 @@ "jsr:@std/assert@^0.223.0": "jsr:@std/assert@0.223.0", "jsr:@std/assert@^0.224.0": "jsr:@std/assert@0.224.0", "jsr:@std/assert@^0.225.1": "jsr:@std/assert@0.225.3", + "jsr:@std/bytes@0.224.0": "jsr:@std/bytes@0.224.0", "jsr:@std/bytes@^0.223.0": "jsr:@std/bytes@0.223.0", "jsr:@std/bytes@^0.224.0": "jsr:@std/bytes@0.224.0", "jsr:@std/bytes@^1.0.0-rc.3": "jsr:@std/bytes@1.0.0", @@ -54,6 +58,7 @@ "jsr:@std/crypto@^0.224.0": "jsr:@std/crypto@0.224.0", "jsr:@std/dotenv@^0.224.0": "jsr:@std/dotenv@0.224.2", "jsr:@std/encoding@0.213.1": "jsr:@std/encoding@0.213.1", + "jsr:@std/encoding@0.224.0": "jsr:@std/encoding@0.224.0", "jsr:@std/encoding@1.0.5": "jsr:@std/encoding@1.0.5", "jsr:@std/encoding@^0.224.0": "jsr:@std/encoding@0.224.3", "jsr:@std/encoding@^0.224.1": "jsr:@std/encoding@0.224.3", @@ -64,8 +69,10 @@ "jsr:@std/io@^0.223.0": "jsr:@std/io@0.223.0", "jsr:@std/io@^0.224": "jsr:@std/io@0.224.8", "jsr:@std/json@^0.223.0": "jsr:@std/json@0.223.0", + "jsr:@std/media-types@0.224.0": "jsr:@std/media-types@0.224.0", "jsr:@std/media-types@^0.224.1": "jsr:@std/media-types@0.224.1", "jsr:@std/path@0.213.1": "jsr:@std/path@0.213.1", + "jsr:@std/path@0.224.0": "jsr:@std/path@0.224.0", "jsr:@std/path@1.0.0-rc.1": "jsr:@std/path@1.0.0-rc.1", "jsr:@std/path@^0.213.1": "jsr:@std/path@0.213.1", "jsr:@std/streams@^0.223.0": "jsr:@std/streams@0.223.0", @@ -263,6 +270,23 @@ "@lambdalisue/async@2.1.1": { "integrity": "1fc9bc6f4ed50215cd2f7217842b18cea80f81c25744f88f8c5eb4be5a1c9ab4" }, + "@negrel/http-ece@0.6.0": { + "integrity": "7afdd81b86ea5b21a9677b323c01c3338705e11cc2bfed250870f5349d8f86f7", + "dependencies": [ + "jsr:@std/bytes@0.224.0", + "jsr:@std/encoding@0.224.0" + ] + }, + "@negrel/webpush@0.3.0": { + "integrity": "5200a56e81668f2debadea228fbeabfe0eda2ee85a56786611dd97950bc51b23", + "dependencies": [ + "jsr:@negrel/http-ece@0.6.0", + "jsr:@std/bytes@0.224.0", + "jsr:@std/encoding@0.224.0", + "jsr:@std/media-types@0.224.0", + "jsr:@std/path@0.224.0" + ] + }, "@nostrify/db@0.35.0": { "integrity": "637191c41812544e361b7997dc44ea098f8bd7efebb28f37a8a7142a0ecada8d", "dependencies": [ @@ -475,6 +499,9 @@ "@std/encoding@0.213.1": { "integrity": "fcbb6928713dde941a18ca5db88ca1544d0755ec8fb20fe61e2dc8144b390c62" }, + "@std/encoding@0.224.0": { + "integrity": "efb6dca97d3e9c31392bd5c8cfd9f9fc9decf5a1f4d1f78af7900a493bcf89b5" + }, "@std/encoding@0.224.3": { "integrity": "5e861b6d81be5359fad4155e591acf17c0207b595112d1840998bb9f476dbdaf" }, @@ -564,6 +591,9 @@ "jsr:@std/streams@^0.223.0" ] }, + "@std/media-types@0.224.0": { + "integrity": "5ac87989393f8cb1c81bee02aef6f5d4c8289b416deabc04f9ad25dff292d0b0" + }, "@std/media-types@0.224.1": { "integrity": "9e69a5daed37c5b5c6d3ce4731dc191f80e67f79bed392b0957d1d03b87f11e1" }, @@ -573,6 +603,12 @@ "jsr:@std/assert@^0.213.1" ] }, + "@std/path@0.224.0": { + "integrity": "55bca6361e5a6d158b9380e82d4981d82d338ec587de02951e2b7c3a24910ee6", + "dependencies": [ + "jsr:@std/assert@^0.224.0" + ] + }, "@std/path@1.0.0-rc.1": { "integrity": "b8c00ae2f19106a6bb7cbf1ab9be52aa70de1605daeb2dbdc4f87a7cbaf10ff6" }, @@ -2149,6 +2185,7 @@ "jsr:@gfx/canvas-wasm@^0.4.2", "jsr:@hono/hono@^4.4.6", "jsr:@lambdalisue/async@^2.1.1", + "jsr:@negrel/webpush@^0.3.0", "jsr:@nostrify/db@^0.35.0", "jsr:@nostrify/nostrify@^0.36.0", "jsr:@nostrify/policies@^0.35.0", diff --git a/src/DittoPush.ts b/src/DittoPush.ts new file mode 100644 index 00000000..52ce1009 --- /dev/null +++ b/src/DittoPush.ts @@ -0,0 +1,36 @@ +import { ApplicationServer, PushMessageOptions, PushSubscriber, PushSubscription } from '@negrel/webpush'; + +import { Conf } from '@/config.ts'; +import { Storages } from '@/storages.ts'; +import { getInstanceMetadata } from '@/utils/instance.ts'; + +export class DittoPush { + static _server: Promise | undefined; + + static get server(): Promise { + if (!this._server) { + this._server = (async () => { + const store = await Storages.db(); + const meta = await getInstanceMetadata(store); + + return await ApplicationServer.new({ + contactInformation: `mailto:${meta.email}`, + vapidKeys: await Conf.vapidKeys, + }); + })(); + } + + return this._server; + } + + static async push( + subscription: PushSubscription, + json: object, + opts: PushMessageOptions = {}, + ): Promise { + const server = await this.server; + const subscriber = new PushSubscriber(server, subscription); + const text = JSON.stringify(json); + return subscriber.pushTextMessage(text, opts); + } +} diff --git a/src/config.ts b/src/config.ts index c7f5e7cb..2634c92c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,5 +1,6 @@ import os from 'node:os'; import ISO6391, { LanguageCode } from 'iso-639-1'; +import { generateVapidKeys } from '@negrel/webpush'; import * as dotenv from '@std/dotenv'; import { getPublicKey, nip19 } from 'nostr-tools'; import { z } from 'zod'; @@ -82,6 +83,9 @@ class Conf { static get pgliteDebug(): 0 | 1 | 2 | 3 | 4 | 5 { return Number(Deno.env.get('PGLITE_DEBUG') || 0) as 0 | 1 | 2 | 3 | 4 | 5; } + static get vapidKeys(): Promise { + return generateVapidKeys(); // FIXME: get the key from environment. + } static db = { /** Database query timeout configurations. */ timeouts: { diff --git a/src/pipeline.ts b/src/pipeline.ts index f854209b..12d377f8 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -4,24 +4,28 @@ import ISO6391 from 'iso-639-1'; import { Kysely, sql } from 'kysely'; import lande from 'lande'; import { LRUCache } from 'lru-cache'; +import { nip19 } from 'nostr-tools'; import { z } from 'zod'; import { Conf } from '@/config.ts'; import { DittoTables } from '@/db/DittoTables.ts'; +import { DittoPush } from '@/DittoPush.ts'; import { DittoEvent } from '@/interfaces/DittoEvent.ts'; import { pipelineEventsCounter, policyEventsCounter } from '@/metrics.ts'; 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 { policyWorker } from '@/workers/policy.ts'; -import { verifyEventWorker } from '@/workers/verify.ts'; import { getAmount } from '@/utils/bolt11.ts'; 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 { policyWorker } from '@/workers/policy.ts'; +import { verifyEventWorker } from '@/workers/verify.ts'; const console = new Stickynotes('ditto:pipeline'); @@ -72,6 +76,7 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise { if (isFresh(event)) { const pubsub = await Storages.pubsub(); await pubsub.event(event); + } +} - // TODO: Web Push +async function webPush(event: NostrEvent): Promise { + if (!isFresh(event)) { + return; + } + + const kysely = await Storages.kysely(); + + const rows = await kysely + .selectFrom('push_subscriptions') + .selectAll() + .where('pubkey', 'in', [...getTagSet(event.tags, 'p')]) + .execute(); + + for (const row of rows) { + const notification = await renderNotification(event, { viewerPubkey: row.pubkey }); + if (!notification) { + continue; + } + + const subscription = { + endpoint: row.endpoint, + keys: { + auth: row.auth, + p256dh: row.p256dh, + }, + }; + + const message: MastodonPush = { + notification_id: notification.id, + notification_type: notification.type, + access_token: nip19.npubEncode(row.pubkey), + }; + + await DittoPush.push(subscription, message); } } diff --git a/src/types/MastodonPush.ts b/src/types/MastodonPush.ts index dc2edbfa..51e54cad 100644 --- a/src/types/MastodonPush.ts +++ b/src/types/MastodonPush.ts @@ -6,10 +6,10 @@ */ export interface MastodonPush { access_token: string; - preferred_locale: string; + preferred_locale?: string; notification_id: string; notification_type: string; - icon: string; - title: string; - body: string; + icon?: string; + title?: string; + body?: string; }