forked from louislam/uptime-kuma
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Nostr dm notifications (louislam#3051)
* Add nostr DM notification provider * require crypto for node 18 compatibility * remove whitespace * move closer to where it is used * simplify success or failure logic * don't clobber the non-alert msg * Update server/notification-providers/nostr.js * polyfills required for node <= 18 * resolve linter warnings * missing comma --------- Co-authored-by: Frank Elsinga <frank@elsinga.de> Co-authored-by: zappityzap <128872140+zappityzap@users.noreply.github.com>
- Loading branch information
Showing
8 changed files
with
427 additions
and
6 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
|
||
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); | ||
|
||
// 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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters