-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Nostr dm notifications #3051
Nostr dm notifications #3051
Changes from all commits
d070a78
cd2d7a7
697e2df
b3045a6
bda0f06
042d774
c2140a6
b48b47b
94c7049
81394ee
9040863
f85969a
23be2d7
e66c4b4
dbeeae1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
const NotificationProvider = require("./notification-provider"); | ||
// polyfill for node <= 18 | ||
global.crypto = require("crypto"); | ||
const { | ||
relayInit, | ||
getPublicKey, | ||
getEventHash, | ||
signEvent, | ||
nip04, | ||
nip19 | ||
} = require("nostr-tools"); | ||
// polyfill for node <= 18 | ||
require("websocket-polyfill"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this included? If you think this is needed, let's split this out of this PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Without it, on Node 18 there is an error from the nostr-tools module. Node 19+ doesn't need it.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please include a comment that this is a |
||
|
||
class Nostr extends NotificationProvider { | ||
name = "nostr"; | ||
|
||
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { | ||
// All DMs should have same timestamp | ||
const createdAt = Math.floor(Date.now() / 1000); | ||
|
||
const senderPrivateKey = await this.getPrivateKey(notification.sender); | ||
const senderPublicKey = getPublicKey(senderPrivateKey); | ||
const recipientsPublicKeys = await this.getPublicKeys(notification.recipients); | ||
zappityzap marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Create NIP-04 encrypted direct message event for each recipient | ||
const events = []; | ||
for (const recipientPublicKey of recipientsPublicKeys) { | ||
const ciphertext = await nip04.encrypt(senderPrivateKey, recipientPublicKey, msg); | ||
let event = { | ||
kind: 4, | ||
pubkey: senderPublicKey, | ||
created_at: createdAt, | ||
tags: [[ "p", recipientPublicKey ]], | ||
content: ciphertext, | ||
}; | ||
event.id = getEventHash(event); | ||
event.sig = signEvent(event, senderPrivateKey); | ||
events.push(event); | ||
} | ||
|
||
// Publish events to each relay | ||
const relays = notification.relays.split("\n"); | ||
let successfulRelays = 0; | ||
|
||
// Connect to each relay | ||
for (const relayUrl of relays) { | ||
const relay = relayInit(relayUrl); | ||
try { | ||
await relay.connect(); | ||
successfulRelays++; | ||
|
||
// Publish events | ||
for (const event of events) { | ||
relay.publish(event); | ||
} | ||
} catch (error) { | ||
continue; | ||
} finally { | ||
relay.close(); | ||
} | ||
} | ||
|
||
// Report success or failure | ||
if (successfulRelays === 0) { | ||
throw Error("Failed to connect to any relays."); | ||
} | ||
return `${successfulRelays}/${relays.length} relays connected.`; | ||
} | ||
|
||
async getPrivateKey(sender) { | ||
try { | ||
const senderDecodeResult = await nip19.decode(sender); | ||
const { data } = senderDecodeResult; | ||
return data; | ||
} catch (error) { | ||
throw new Error(`Failed to get private key: ${error.message}`); | ||
} | ||
} | ||
|
||
async getPublicKeys(recipients) { | ||
const recipientsList = recipients.split("\n"); | ||
const publicKeys = []; | ||
for (const recipient of recipientsList) { | ||
try { | ||
const recipientDecodeResult = await nip19.decode(recipient); | ||
const { type, data } = recipientDecodeResult; | ||
if (type === "npub") { | ||
publicKeys.push(data); | ||
} else { | ||
throw new Error("not an npub"); | ||
} | ||
} catch (error) { | ||
throw new Error(`Error decoding recipient: ${error}`); | ||
} | ||
} | ||
return publicKeys; | ||
} | ||
} | ||
|
||
module.exports = Nostr; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<template> | ||
<div class="mb-3"> | ||
<label for="nostr-relays" class="form-label">{{ $t("nostrRelays") }}<span style="color: red;"><sup>*</sup></span></label> | ||
<textarea id="nostr-relays" v-model="$parent.notification.relays" class="form-control" :required="true" placeholder="wss://127.0.0.1:7777/"></textarea> | ||
<small class="form-text text-muted">{{ $t("nostrRelaysHelp") }}</small> | ||
</div> | ||
<div class="mb-3"> | ||
<label for="nostr-sender" class="form-label">{{ $t("nostrSender") }}<span style="color: red;"><sup>*</sup></span></label> | ||
<HiddenInput id="nostr-sender" v-model="$parent.notification.sender" autocomplete="new-password" :required="true"></HiddenInput> | ||
</div> | ||
<div class="mb-3"> | ||
<label for="nostr-recipients" class="form-label">{{ $t("nostrRecipients") }}<span style="color: red;"><sup>*</sup></span></label> | ||
<textarea id="nostr-recipients" v-model="$parent.notification.recipients" class="form-control" :required="true" placeholder="npub123... npub789..."></textarea> | ||
<small class="form-text text-muted">{{ $t("nostrRecipientsHelp") }}</small> | ||
</div> | ||
</template> | ||
|
||
<script> | ||
import HiddenInput from "../HiddenInput.vue"; | ||
|
||
export default { | ||
components: { | ||
HiddenInput, | ||
}, | ||
}; | ||
</script> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this line included?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without it, on Node 18 there is an error from the nostr-tools module that crypto is not defined. Node 19+ doesn't need it. There is a closed issue for nostr-tools about it: nbd-wtf/nostr-tools#192
I see they have a better method for Node 18 compatibility now, but I'm not sure how to implement it:
Here is the error with Node 18:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please include a comment that this is a
polyfill for node <= 18