Actually push ??

This commit is contained in:
Alex Gleason 2024-10-08 18:37:23 -05:00
parent 1ed6cac1c4
commit 8823c0987d
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
6 changed files with 125 additions and 7 deletions

View file

@ -40,6 +40,7 @@
"@hono/hono": "jsr:@hono/hono@^4.4.6", "@hono/hono": "jsr:@hono/hono@^4.4.6",
"@isaacs/ttlcache": "npm:@isaacs/ttlcache@^1.4.1", "@isaacs/ttlcache": "npm:@isaacs/ttlcache@^1.4.1",
"@lambdalisue/async": "jsr:@lambdalisue/async@^2.1.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", "@noble/secp256k1": "npm:@noble/secp256k1@^2.0.0",
"@nostrify/db": "jsr:@nostrify/db@^0.35.0", "@nostrify/db": "jsr:@nostrify/db@^0.35.0",
"@nostrify/nostrify": "jsr:@nostrify/nostrify@^0.36.0", "@nostrify/nostrify": "jsr:@nostrify/nostrify@^0.36.0",

37
deno.lock generated
View file

@ -22,6 +22,9 @@
"jsr:@gleasonator/policy@0.7.1": "jsr:@gleasonator/policy@0.7.1", "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:@hono/hono@^4.4.6": "jsr:@hono/hono@4.6.2",
"jsr:@lambdalisue/async@^2.1.1": "jsr:@lambdalisue/async@2.1.1", "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/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.1": "jsr:@nostrify/nostrify@0.22.5",
"jsr:@nostrify/nostrify@^0.22.4": "jsr:@nostrify/nostrify@0.22.4", "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.223.0": "jsr:@std/assert@0.223.0",
"jsr:@std/assert@^0.224.0": "jsr:@std/assert@0.224.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/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.223.0": "jsr:@std/bytes@0.223.0",
"jsr:@std/bytes@^0.224.0": "jsr:@std/bytes@0.224.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", "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/crypto@^0.224.0": "jsr:@std/crypto@0.224.0",
"jsr:@std/dotenv@^0.224.0": "jsr:@std/dotenv@0.224.2", "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.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@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.0": "jsr:@std/encoding@0.224.3",
"jsr:@std/encoding@^0.224.1": "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.223.0": "jsr:@std/io@0.223.0",
"jsr:@std/io@^0.224": "jsr:@std/io@0.224.8", "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/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/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.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@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/path@^0.213.1": "jsr:@std/path@0.213.1",
"jsr:@std/streams@^0.223.0": "jsr:@std/streams@0.223.0", "jsr:@std/streams@^0.223.0": "jsr:@std/streams@0.223.0",
@ -263,6 +270,23 @@
"@lambdalisue/async@2.1.1": { "@lambdalisue/async@2.1.1": {
"integrity": "1fc9bc6f4ed50215cd2f7217842b18cea80f81c25744f88f8c5eb4be5a1c9ab4" "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": { "@nostrify/db@0.35.0": {
"integrity": "637191c41812544e361b7997dc44ea098f8bd7efebb28f37a8a7142a0ecada8d", "integrity": "637191c41812544e361b7997dc44ea098f8bd7efebb28f37a8a7142a0ecada8d",
"dependencies": [ "dependencies": [
@ -475,6 +499,9 @@
"@std/encoding@0.213.1": { "@std/encoding@0.213.1": {
"integrity": "fcbb6928713dde941a18ca5db88ca1544d0755ec8fb20fe61e2dc8144b390c62" "integrity": "fcbb6928713dde941a18ca5db88ca1544d0755ec8fb20fe61e2dc8144b390c62"
}, },
"@std/encoding@0.224.0": {
"integrity": "efb6dca97d3e9c31392bd5c8cfd9f9fc9decf5a1f4d1f78af7900a493bcf89b5"
},
"@std/encoding@0.224.3": { "@std/encoding@0.224.3": {
"integrity": "5e861b6d81be5359fad4155e591acf17c0207b595112d1840998bb9f476dbdaf" "integrity": "5e861b6d81be5359fad4155e591acf17c0207b595112d1840998bb9f476dbdaf"
}, },
@ -564,6 +591,9 @@
"jsr:@std/streams@^0.223.0" "jsr:@std/streams@^0.223.0"
] ]
}, },
"@std/media-types@0.224.0": {
"integrity": "5ac87989393f8cb1c81bee02aef6f5d4c8289b416deabc04f9ad25dff292d0b0"
},
"@std/media-types@0.224.1": { "@std/media-types@0.224.1": {
"integrity": "9e69a5daed37c5b5c6d3ce4731dc191f80e67f79bed392b0957d1d03b87f11e1" "integrity": "9e69a5daed37c5b5c6d3ce4731dc191f80e67f79bed392b0957d1d03b87f11e1"
}, },
@ -573,6 +603,12 @@
"jsr:@std/assert@^0.213.1" "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": { "@std/path@1.0.0-rc.1": {
"integrity": "b8c00ae2f19106a6bb7cbf1ab9be52aa70de1605daeb2dbdc4f87a7cbaf10ff6" "integrity": "b8c00ae2f19106a6bb7cbf1ab9be52aa70de1605daeb2dbdc4f87a7cbaf10ff6"
}, },
@ -2149,6 +2185,7 @@
"jsr:@gfx/canvas-wasm@^0.4.2", "jsr:@gfx/canvas-wasm@^0.4.2",
"jsr:@hono/hono@^4.4.6", "jsr:@hono/hono@^4.4.6",
"jsr:@lambdalisue/async@^2.1.1", "jsr:@lambdalisue/async@^2.1.1",
"jsr:@negrel/webpush@^0.3.0",
"jsr:@nostrify/db@^0.35.0", "jsr:@nostrify/db@^0.35.0",
"jsr:@nostrify/nostrify@^0.36.0", "jsr:@nostrify/nostrify@^0.36.0",
"jsr:@nostrify/policies@^0.35.0", "jsr:@nostrify/policies@^0.35.0",

36
src/DittoPush.ts Normal file
View file

@ -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<ApplicationServer> | undefined;
static get server(): Promise<ApplicationServer> {
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<void> {
const server = await this.server;
const subscriber = new PushSubscriber(server, subscription);
const text = JSON.stringify(json);
return subscriber.pushTextMessage(text, opts);
}
}

View file

@ -1,5 +1,6 @@
import os from 'node:os'; import os from 'node:os';
import ISO6391, { LanguageCode } from 'iso-639-1'; import ISO6391, { LanguageCode } from 'iso-639-1';
import { generateVapidKeys } from '@negrel/webpush';
import * as dotenv from '@std/dotenv'; import * as dotenv from '@std/dotenv';
import { getPublicKey, nip19 } from 'nostr-tools'; import { getPublicKey, nip19 } from 'nostr-tools';
import { z } from 'zod'; import { z } from 'zod';
@ -82,6 +83,9 @@ class Conf {
static get pgliteDebug(): 0 | 1 | 2 | 3 | 4 | 5 { static get pgliteDebug(): 0 | 1 | 2 | 3 | 4 | 5 {
return Number(Deno.env.get('PGLITE_DEBUG') || 0) as 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<CryptoKeyPair> {
return generateVapidKeys(); // FIXME: get the key from environment.
}
static db = { static db = {
/** Database query timeout configurations. */ /** Database query timeout configurations. */
timeouts: { timeouts: {

View file

@ -4,24 +4,28 @@ import ISO6391 from 'iso-639-1';
import { Kysely, sql } from 'kysely'; import { Kysely, sql } from 'kysely';
import lande from 'lande'; import lande from 'lande';
import { LRUCache } from 'lru-cache'; import { LRUCache } from 'lru-cache';
import { nip19 } from 'nostr-tools';
import { z } from 'zod'; import { z } from 'zod';
import { Conf } from '@/config.ts'; import { Conf } from '@/config.ts';
import { DittoTables } from '@/db/DittoTables.ts'; import { DittoTables } from '@/db/DittoTables.ts';
import { DittoPush } from '@/DittoPush.ts';
import { DittoEvent } from '@/interfaces/DittoEvent.ts'; import { DittoEvent } from '@/interfaces/DittoEvent.ts';
import { pipelineEventsCounter, policyEventsCounter } from '@/metrics.ts'; import { pipelineEventsCounter, policyEventsCounter } from '@/metrics.ts';
import { RelayError } from '@/RelayError.ts'; import { RelayError } from '@/RelayError.ts';
import { AdminSigner } from '@/signers/AdminSigner.ts'; import { AdminSigner } from '@/signers/AdminSigner.ts';
import { hydrateEvents } from '@/storages/hydrate.ts'; import { hydrateEvents } from '@/storages/hydrate.ts';
import { Storages } from '@/storages.ts'; import { Storages } from '@/storages.ts';
import { MastodonPush } from '@/types/MastodonPush.ts';
import { eventAge, parseNip05, Time } from '@/utils.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 { getAmount } from '@/utils/bolt11.ts';
import { nip05Cache } from '@/utils/nip05.ts'; import { nip05Cache } from '@/utils/nip05.ts';
import { purifyEvent } from '@/utils/purify.ts'; import { purifyEvent } from '@/utils/purify.ts';
import { updateStats } from '@/utils/stats.ts'; import { updateStats } from '@/utils/stats.ts';
import { getTagSet } from '@/utils/tags.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'); const console = new Stickynotes('ditto:pipeline');
@ -72,6 +76,7 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise<void
} finally { } finally {
await generateSetEvents(event); await generateSetEvents(event);
await streamOut(event); await streamOut(event);
await webPush(event);
} }
} }
@ -230,8 +235,43 @@ async function streamOut(event: NostrEvent): Promise<void> {
if (isFresh(event)) { if (isFresh(event)) {
const pubsub = await Storages.pubsub(); const pubsub = await Storages.pubsub();
await pubsub.event(event); await pubsub.event(event);
}
}
// TODO: Web Push async function webPush(event: NostrEvent): Promise<void> {
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);
} }
} }

View file

@ -6,10 +6,10 @@
*/ */
export interface MastodonPush { export interface MastodonPush {
access_token: string; access_token: string;
preferred_locale: string; preferred_locale?: string;
notification_id: string; notification_id: string;
notification_type: string; notification_type: string;
icon: string; icon?: string;
title: string; title?: string;
body: string; body?: string;
} }