Skip to content
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

feat: add endpoint for registering alby go notifications #128

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func main() {
e.POST("/nip47", svc.NIP47Handler)
e.POST("/nip47/webhook", svc.NIP47WebhookHandler)
e.POST("/nip47/notifications", svc.NIP47NotificationHandler)
e.POST("/nip47/notifications/go", svc.NIP47ExpoNotificationHandler)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't use app specific names here.
You implement an universal API here that can also be used by other applications.
(e.g. /nip47/notifications/expo)

Also try to be consistent.
maybe we move the webhook endpoin then to:
/nip47/notifications/webhook

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done 👍

Regarding webhook, it's a one-time notification endpoint, and it would also be a breaking change. Should we do that?

im-adithya marked this conversation as resolved.
Show resolved Hide resolved
e.POST("/publish", svc.PublishHandler)
e.POST("/subscriptions", svc.SubscriptionHandler)
e.DELETE("/subscriptions/:id", svc.StopSubscriptionHandler)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/oliveroneill/exponent-server-sdk-golang v0.0.0-20210823140141-d050598be512 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/puzpuzpuz/xsync/v3 v3.1.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/nbd-wtf/go-nostr v0.31.4 h1:Qq+PHyKixRZR6Tn+omF7LykK/IR6qcdmThNY132pKsA=
github.com/nbd-wtf/go-nostr v0.31.4/go.mod h1:vHKtHyLXDXzYBN0fi/9Y/Q5AD0p+hk8TQVKlldAi0gI=
github.com/oliveroneill/exponent-server-sdk-golang v0.0.0-20210823140141-d050598be512 h1:/ZSmjwl1inqsiHMhn+sPlEtSHdVTf+TH3LNGGdMQ/vA=
github.com/oliveroneill/exponent-server-sdk-golang v0.0.0-20210823140141-d050598be512/go.mod h1:Isv/48UnAjtxS8FD80Bito3ZJqZRyIMxKARIEITfW4k=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/outcaste-io/ristretto v0.2.3 h1:AK4zt/fJ76kjlYObOeNwh4T3asEuaCmp26pOvUOL9w0=
Expand Down
164 changes: 164 additions & 0 deletions internal/nostr/expo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package nostr

import (
"net/http"
"time"

"github.com/labstack/echo/v4"
"github.com/nbd-wtf/go-nostr"
expo "github.com/oliveroneill/exponent-server-sdk-golang/sdk"
"github.com/sirupsen/logrus"
)

func (svc *Service) NIP47ExpoNotificationHandler(c echo.Context) error {
var requestData NIP47ExpoNotificationRequest
if err := c.Bind(&requestData); err != nil {
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: "Error decoding notification request",
Error: err.Error(),
})
}

if (requestData.PushToken == "") {
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: "push token is empty",
Error: "no push token in request data",
})
}

_, err := expo.NewExponentPushToken(requestData.PushToken)
if err != nil {
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: "invalid push token",
Error: "invalid push token in request data",
})
}

if (requestData.WalletPubkey == "") {
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: "wallet pubkey is empty",
Error: "no wallet pubkey in request data",
})
}

if (requestData.ConnPubkey == "") {
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: "connection pubkey is empty",
Error: "no connection pubkey in request data",
})
}

var existingSubscriptions []Subscription
if err := svc.db.Where("push_token = ? AND open = ?", requestData.PushToken, true).Find(&existingSubscriptions).Error; err != nil {
svc.Logger.WithError(err).WithFields(logrus.Fields{
"push_token": requestData.PushToken,
}).Error("Failed to check existing subscriptions")
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: "internal server error",
Error: err.Error(),
})
}

for _, existingSubscription := range existingSubscriptions {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could the DB query above be improved instead of having to do a for loop after (more filtering?)

existingWalletPubkey := (*existingSubscription.Authors)[0]
existingConnPubkey := (*existingSubscription.Tags)["p"][0]

if existingWalletPubkey == requestData.WalletPubkey && existingConnPubkey == requestData.ConnPubkey {
svc.Logger.WithFields(logrus.Fields{
"wallet_pubkey": requestData.WalletPubkey,
"relay_url": requestData.RelayUrl,
"push_token": requestData.PushToken,
}).Debug("Subscription already started")
return c.JSON(http.StatusOK, ExpoSubscriptionResponse{
SubscriptionId: existingSubscription.Uuid,
PushToken: requestData.PushToken,
WalletPubkey: requestData.WalletPubkey,
AppPubkey: requestData.ConnPubkey,
})
}
}

svc.Logger.WithFields(logrus.Fields{
"wallet_pubkey": requestData.WalletPubkey,
"relay_url": requestData.RelayUrl,
"push_token": requestData.PushToken,
}).Debug("Subscribing to send push notifications")

subscription := Subscription{
RelayUrl: requestData.RelayUrl,
PushToken: requestData.PushToken,
Open: true,
Since: time.Now(),
Authors: &[]string{requestData.WalletPubkey},
Kinds: &[]int{NIP_47_NOTIFICATION_KIND},
}

tags := make(nostr.TagMap)
(tags)["p"] = []string{requestData.ConnPubkey}
subscription.Tags = &tags

err = svc.db.Create(&subscription).Error
if err != nil {
svc.Logger.WithError(err).WithFields(logrus.Fields{
"wallet_pubkey": requestData.WalletPubkey,
"relay_url": requestData.RelayUrl,
"push_token": requestData.PushToken,
}).Error("Failed to store subscription")
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: "Failed to store subscription",
Error: err.Error(),
})
}

go svc.startSubscription(svc.Ctx, &subscription, nil, svc.handleSubscribedExpoNotification)

return c.JSON(http.StatusOK, ExpoSubscriptionResponse{
SubscriptionId: subscription.Uuid,
PushToken: requestData.PushToken,
WalletPubkey: requestData.WalletPubkey,
AppPubkey: requestData.ConnPubkey,
})
}

func (svc *Service) handleSubscribedExpoNotification(event *nostr.Event, subscription *Subscription) {
svc.Logger.WithFields(logrus.Fields{
"event_id": event.ID,
"event_kind": event.Kind,
"subscription_id": subscription.ID,
"relay_url": subscription.RelayUrl,
}).Debug("Received subscribed notification")

pushToken, _ := expo.NewExponentPushToken(subscription.PushToken)

response, err := svc.client.Publish(
&expo.PushMessage{
To: []expo.ExponentPushToken{pushToken},
Title: "New event",
Body: "",
Data: map[string]string{
"content": event.Content,
"appPubkey": event.Tags.GetFirst([]string{"p", ""}).Value(),
},
Priority: expo.DefaultPriority,
},
)
if err != nil {
svc.Logger.WithError(err).WithFields(logrus.Fields{
"push_token": subscription.PushToken,
}).Error("Failed to send expo notification")
return
}

err = response.ValidateResponse()
if err != nil {
svc.Logger.WithError(err).WithFields(logrus.Fields{
"push_token": subscription.PushToken,
}).Error("Failed to valid expo publish response")
return
}

svc.Logger.WithFields(logrus.Fields{
"event_id": event.ID,
"push_token": subscription.PushToken,
}).Debug("Push notification sent successfully")
}
17 changes: 16 additions & 1 deletion internal/nostr/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type Subscription struct {
ID uint
RelayUrl string
WebhookUrl string
PushToken string
Open bool
Ids *[]string `gorm:"-"`
Kinds *[]int `gorm:"-"`
Expand Down Expand Up @@ -185,6 +186,13 @@ type NIP47NotificationRequest struct {
ConnPubkey string `json:"connectionPubkey"`
}

type NIP47ExpoNotificationRequest struct {
RelayUrl string `json:"relayUrl"`
PushToken string `json:"pushToken"`
WalletPubkey string `json:"walletPubkey"`
ConnPubkey string `json:"connectionPubkey"`
rolznz marked this conversation as resolved.
Show resolved Hide resolved
}

type NIP47Response struct {
Event *nostr.Event `json:"event,omitempty"`
State string `json:"state"`
Expand All @@ -208,10 +216,17 @@ type SubscriptionRequest struct {
}

type SubscriptionResponse struct {
SubscriptionId string `json:"subscription_id"`
SubscriptionId string `json:"subscriptionId"`
im-adithya marked this conversation as resolved.
Show resolved Hide resolved
WebhookUrl string `json:"webhookUrl"`
}

type ExpoSubscriptionResponse struct {
SubscriptionId string `json:"subscriptionId"`
PushToken string `json:"pushToken"`
WalletPubkey string `json:"walletPubkey"`
AppPubkey string `json:"appPubkey"`
}

type StopSubscriptionResponse struct {
Message string `json:"message"`
State string `json:"state"`
Expand Down
16 changes: 15 additions & 1 deletion internal/nostr/nostr.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"gorm.io/gorm"

"github.com/jackc/pgx/v5/stdlib"
expo "github.com/oliveroneill/exponent-server-sdk-golang/sdk"
sqltrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql"
gormtrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/gorm.io/gorm.v1"
)
Expand All @@ -49,6 +50,7 @@ type Service struct {
subscriptions map[string]*nostr.Subscription
subscriptionsMutex sync.Mutex
relayMutex sync.Mutex
client *expo.PushClient
}

func NewService(ctx context.Context) (*Service, error) {
Expand Down Expand Up @@ -116,6 +118,12 @@ func NewService(ctx context.Context) (*Service, error) {

subscriptions := make(map[string]*nostr.Subscription)

// TODO: Check limits
client := expo.NewPushClient(&expo.ClientConfig{
Host: "https://api.expo.dev",
APIURL: "/v2",
})

im-adithya marked this conversation as resolved.
Show resolved Hide resolved
var wg sync.WaitGroup
svc := &Service{
Cfg: cfg,
Expand All @@ -125,6 +133,7 @@ func NewService(ctx context.Context) (*Service, error) {
Logger: logger,
Relay: relay,
subscriptions: subscriptions,
client: client,
}

logger.Info("Starting all open subscriptions...")
Expand All @@ -139,7 +148,11 @@ func NewService(ctx context.Context) (*Service, error) {
// Create a copy of the loop variable to
// avoid passing address of the same variable
subscription := sub
go svc.startSubscription(svc.Ctx, &subscription, nil, svc.handleSubscribedEvent)
handleEvent := svc.handleSubscribedEvent
if sub.PushToken != "" {
handleEvent = svc.handleSubscribedExpoNotification
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the naming be improved here? we are still handling a subscribed event, right - we're just notifying in a different way?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to be simple - handlePushNotification, wdyt?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's still inconsistent with handleSubscribedEvent

}
go svc.startSubscription(svc.Ctx, &subscription, nil, handleEvent)
}

return svc, nil
Expand Down Expand Up @@ -928,6 +941,7 @@ func (svc *Service) postEventToWebhook(event *nostr.Event, webhookURL string) {
"event_kind": event.Kind,
"webhook_url": webhookURL,
}).Error("Failed to post event to webhook")
return
}

svc.Logger.WithFields(logrus.Fields{
Expand Down
2 changes: 1 addition & 1 deletion migrations/202404021628_add_uuid_to_subscriptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ var _202404021628_add_uuid_to_subscriptions = &gormigrate.Migration{
}
return tx.Exec("ALTER TABLE subscriptions DROP COLUMN IF EXISTS uuid").Error
},
}
}
2 changes: 1 addition & 1 deletion migrations/202404031539_add_indexes.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ var _202404031539_add_indexes = &gormigrate.Migration{
}
return nil
},
}
}
29 changes: 29 additions & 0 deletions migrations/202411071013_add_push_token_to_subscriptions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package migrations

import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)

// Add push_token column to subscriptions table
var _202411071013_add_push_token_to_subscriptions = &gormigrate.Migration{
ID: "202411071013_add_push_token_to_subscriptions",
Migrate: func(tx *gorm.DB) error {
if err := tx.Exec("ALTER TABLE subscriptions ADD COLUMN push_token TEXT").Error; err != nil {
return err
}
if err := tx.Exec("CREATE INDEX IF NOT EXISTS subscriptions_push_token ON subscriptions (push_token)").Error; err != nil {
return err
}
return nil
},
Rollback: func(tx *gorm.DB) error {
if err := tx.Exec("DROP INDEX IF EXISTS subscriptions_push_token").Error; err != nil {
return err
}
if err := tx.Exec("ALTER TABLE subscriptions DROP COLUMN push_token").Error; err != nil {
return err
}
return nil
},
}
3 changes: 2 additions & 1 deletion migrations/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ func Migrate(db *gorm.DB) error {
_202404021628_add_uuid_to_subscriptions,
_202404031539_add_indexes,
_202407171220_add_response_received_at_to_request_events,
_202411071013_add_push_token_to_subscriptions,
})

return m.Migrate()
}
}
Loading