mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
243 lines
7.2 KiB
Go
243 lines
7.2 KiB
Go
package notifications
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
|
|
"github.com/getAlby/go-nostr"
|
|
"github.com/getAlby/hub/config"
|
|
"github.com/getAlby/hub/constants"
|
|
"github.com/getAlby/hub/db"
|
|
"github.com/getAlby/hub/events"
|
|
"github.com/getAlby/hub/logger"
|
|
"github.com/getAlby/hub/nip47/cipher"
|
|
"github.com/getAlby/hub/nip47/models"
|
|
"github.com/getAlby/hub/nip47/permissions"
|
|
nostrmodels "github.com/getAlby/hub/nostr/models"
|
|
"github.com/getAlby/hub/service/keys"
|
|
"github.com/sirupsen/logrus"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type Nip47Notifier struct {
|
|
pool nostrmodels.SimplePool
|
|
cfg config.Config
|
|
keys keys.Keys
|
|
db *gorm.DB
|
|
permissionsSvc permissions.PermissionsService
|
|
}
|
|
|
|
func NewNip47Notifier(pool nostrmodels.SimplePool, db *gorm.DB, cfg config.Config, keys keys.Keys, permissionsSvc permissions.PermissionsService) *Nip47Notifier {
|
|
return &Nip47Notifier{
|
|
pool: pool,
|
|
cfg: cfg,
|
|
db: db,
|
|
permissionsSvc: permissionsSvc,
|
|
keys: keys,
|
|
}
|
|
}
|
|
|
|
func (notifier *Nip47Notifier) ConsumeEvent(ctx context.Context, event *events.Event) error {
|
|
switch event.Event {
|
|
case "nwc_payment_received":
|
|
transaction, ok := event.Properties.(*db.Transaction)
|
|
if !ok {
|
|
logger.Logger.WithField("event", event).Error("Failed to cast event")
|
|
return errors.New("failed to cast event")
|
|
}
|
|
|
|
notification := PaymentReceivedNotification{
|
|
Transaction: *models.ToNip47Transaction(transaction),
|
|
}
|
|
|
|
notifier.notifySubscribers(ctx, &Notification{
|
|
Notification: notification,
|
|
NotificationType: PAYMENT_RECEIVED_NOTIFICATION,
|
|
}, nostr.Tags{}, transaction.AppId)
|
|
|
|
case "nwc_payment_sent":
|
|
transaction, ok := event.Properties.(*db.Transaction)
|
|
if !ok {
|
|
logger.Logger.WithField("event", event).Error("Failed to cast event")
|
|
return errors.New("failed to cast event")
|
|
}
|
|
|
|
notification := PaymentSentNotification{
|
|
Transaction: *models.ToNip47Transaction(transaction),
|
|
}
|
|
|
|
notifier.notifySubscribers(ctx, &Notification{
|
|
Notification: notification,
|
|
NotificationType: PAYMENT_SENT_NOTIFICATION,
|
|
}, nostr.Tags{}, transaction.AppId)
|
|
|
|
case "nwc_hold_invoice_accepted":
|
|
dbTransaction, ok := event.Properties.(*db.Transaction)
|
|
if !ok {
|
|
logger.Logger.WithField("event", event).Error("Failed to cast event properties to db.Transaction for hold invoice accepted")
|
|
return errors.New("failed to cast event")
|
|
}
|
|
|
|
nip47Transaction := models.ToNip47Transaction(dbTransaction)
|
|
|
|
notification := HoldInvoiceAcceptedNotification{
|
|
Transaction: *nip47Transaction,
|
|
}
|
|
|
|
notifier.notifySubscribers(ctx, &Notification{
|
|
Notification: notification,
|
|
NotificationType: HOLD_INVOICE_ACCEPTED_NOTIFICATION,
|
|
}, nostr.Tags{}, dbTransaction.AppId)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (notifier *Nip47Notifier) notifySubscribers(ctx context.Context, notification *Notification, tags nostr.Tags, appId *uint) error {
|
|
apps := []db.App{}
|
|
|
|
// TODO: join apps and permissions
|
|
err := notifier.db.Find(&apps).Error
|
|
if err != nil {
|
|
logger.Logger.WithError(err).Error("Failed to list apps")
|
|
return errors.New("failed to list apps")
|
|
}
|
|
|
|
for _, app := range apps {
|
|
if app.Isolated && (appId == nil || app.ID != *appId) {
|
|
continue
|
|
}
|
|
|
|
hasPermission, _, _ := notifier.permissionsSvc.HasPermission(&app, constants.NOTIFICATIONS_SCOPE)
|
|
if !hasPermission {
|
|
continue
|
|
}
|
|
|
|
appWalletPrivKey := notifier.keys.GetNostrSecretKey()
|
|
if app.WalletPubkey != nil {
|
|
appWalletPrivKey, err = notifier.keys.GetAppWalletKey(app.ID)
|
|
if err != nil {
|
|
logger.Logger.WithFields(logrus.Fields{
|
|
"notification": notification,
|
|
"appId": app.ID,
|
|
}).WithError(err).Error("error deriving child key")
|
|
return errors.New("failed to derive child key")
|
|
}
|
|
}
|
|
|
|
appWalletPubKey, err := nostr.GetPublicKey(appWalletPrivKey)
|
|
if err != nil {
|
|
logger.Logger.WithFields(logrus.Fields{
|
|
"notification": notification,
|
|
"appId": app.ID,
|
|
}).WithError(err).Error("Failed to calculate app wallet pub key")
|
|
return errors.New("failed to calculate app wallet pubkey")
|
|
}
|
|
|
|
err = notifier.notifySubscriber(ctx, &app, notification, tags, appWalletPubKey, appWalletPrivKey, constants.ENCRYPTION_TYPE_NIP04)
|
|
if err != nil {
|
|
logger.Logger.WithError(err).Error("failed to notify subscriber (NIP-04)")
|
|
return err
|
|
}
|
|
err = notifier.notifySubscriber(ctx, &app, notification, tags, appWalletPubKey, appWalletPrivKey, constants.ENCRYPTION_TYPE_NIP44_V2)
|
|
if err != nil {
|
|
logger.Logger.WithError(err).Error("failed to notify subscriber (NIP-44)")
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (notifier *Nip47Notifier) notifySubscriber(ctx context.Context, app *db.App, notification *Notification, tags nostr.Tags, appWalletPubKey, appWalletPrivKey string, encryption string) error {
|
|
logger.Logger.WithFields(logrus.Fields{
|
|
"notification": notification,
|
|
"appId": app.ID,
|
|
"encryption": encryption,
|
|
}).Debug("Notifying subscriber")
|
|
|
|
var err error
|
|
|
|
payloadBytes, err := json.Marshal(notification)
|
|
if err != nil {
|
|
logger.Logger.WithFields(logrus.Fields{
|
|
"notification": notification,
|
|
"appId": app.ID,
|
|
"encryption": encryption,
|
|
}).WithError(err).Error("Failed to stringify notification")
|
|
return err
|
|
}
|
|
|
|
nip47Cipher, err := cipher.NewNip47Cipher(encryption, app.AppPubkey, appWalletPrivKey)
|
|
if err != nil {
|
|
logger.Logger.WithFields(logrus.Fields{
|
|
"notification": notification,
|
|
"appId": app.ID,
|
|
"encryption": encryption,
|
|
}).WithError(err).Error("Failed to initialize cipher")
|
|
return err
|
|
}
|
|
|
|
msg, err := nip47Cipher.Encrypt(string(payloadBytes))
|
|
if err != nil {
|
|
logger.Logger.WithFields(logrus.Fields{
|
|
"notification": notification,
|
|
"appId": app.ID,
|
|
"encryption": encryption,
|
|
}).WithError(err).Error("Failed to encrypt notification payload")
|
|
return err
|
|
}
|
|
|
|
allTags := nostr.Tags{[]string{"p", app.AppPubkey}}
|
|
allTags = append(allTags, tags...)
|
|
|
|
event := &nostr.Event{
|
|
PubKey: appWalletPubKey,
|
|
CreatedAt: nostr.Now(),
|
|
Kind: models.NOTIFICATION_KIND,
|
|
Tags: allTags,
|
|
Content: msg,
|
|
}
|
|
|
|
if encryption == constants.ENCRYPTION_TYPE_NIP04 {
|
|
event.Kind = models.LEGACY_NOTIFICATION_KIND
|
|
}
|
|
|
|
err = event.Sign(appWalletPrivKey)
|
|
if err != nil {
|
|
logger.Logger.WithFields(logrus.Fields{
|
|
"notification": notification,
|
|
"appId": app.ID,
|
|
"encryption": encryption,
|
|
}).WithError(err).Error("Failed to sign event")
|
|
return err
|
|
}
|
|
|
|
publishResultChannel := notifier.pool.PublishMany(ctx, notifier.cfg.GetRelayUrls(), *event)
|
|
|
|
publishSuccessful := false
|
|
for result := range publishResultChannel {
|
|
if result.Error == nil {
|
|
publishSuccessful = true
|
|
} else {
|
|
logger.Logger.WithFields(logrus.Fields{
|
|
"notification": notification,
|
|
"appId": app.ID,
|
|
"relay": result.RelayURL,
|
|
}).WithError(result.Error).Error("failed to publish notification to relay")
|
|
}
|
|
}
|
|
|
|
if !publishSuccessful {
|
|
logger.Logger.WithFields(logrus.Fields{
|
|
"notification": notification,
|
|
"appId": app.ID,
|
|
"encryption": encryption,
|
|
}).WithError(err).Error("Failed to publish notification")
|
|
return err
|
|
}
|
|
logger.Logger.WithFields(logrus.Fields{
|
|
"appId": app.ID,
|
|
"encryption": encryption,
|
|
}).Debug("Published notification event")
|
|
return nil
|
|
}
|