Skip to content

Commit

Permalink
feat(SPV-848): webhook subscribe
Browse files Browse the repository at this point in the history
  • Loading branch information
chris-4chain committed Jun 27, 2024
1 parent 545095a commit 51f105c
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 0 deletions.
5 changes: 5 additions & 0 deletions examples/Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,8 @@ tasks:
cmds:
- echo "running generate_totp..."
- go run ./generate_totp/generate_totp.go
webhooks:
desc: "running webhooks..."
cmds:
- echo "running webhooks..."
- go run ./webhooks/webhooks.go
41 changes: 41 additions & 0 deletions examples/webhooks/webhooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
Package main - send_op_return example
*/
package main

import (
"context"
"fmt"
"net/http"
"time"

walletclient "github.com/bitcoin-sv/spv-wallet-go-client"
"github.com/bitcoin-sv/spv-wallet-go-client/examples"
)

func main() {
defer examples.HandlePanic()

client := walletclient.NewWithAdminKey("http://localhost:3003/v1", examples.ExampleAdminKey)
wh := walletclient.NewWebhook(client, "http://localhost:5005/notification", "", "")
err := wh.Subscribe(context.Background())
if err != nil {
panic(err)
}

http.Handle("/notification", wh.HTTPHandler())

go func() {
for {
select {
case event := <-wh.Channel:
time.Sleep(100 * time.Millisecond) // simulate processing time
fmt.Println(event)
case <-context.Background().Done():
return
}
}
}()

http.ListenAndServe(":5005", nil)
}
18 changes: 18 additions & 0 deletions http.go
Original file line number Diff line number Diff line change
Expand Up @@ -1128,3 +1128,21 @@ func (wc *WalletClient) SendToRecipients(ctx context.Context, recipients []*Reci

return wc.RecordTransaction(ctx, hex, draft.ID, metadata)
}

func (wc *WalletClient) AdminSubscribeWebhook(ctx context.Context, webhookURL, tokenHeader, tokenValue string) ResponseError {
requestModel := struct {
URL string `json:"url"`
TokenHeader string `json:"tokenHeader"`
TokenValue string `json:"tokenValue"`
}{
URL: webhookURL,
TokenHeader: tokenHeader,
TokenValue: tokenValue,
}
rawJSON, err := json.Marshal(requestModel)
if err != nil {
return WrapError(nil)
}
err = wc.doHTTPRequest(ctx, http.MethodPost, "/admin/webhooks/subscribe", rawJSON, wc.adminXPriv, true, nil)
return WrapError(err)
}
62 changes: 62 additions & 0 deletions webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package walletclient

import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
)

const (
eventBufferLength = 100
)

type Event any

type Webhook struct {
URL string
TokenHeader string
TokenValue string
Channel chan Event

client *WalletClient
}

func NewWebhook(client *WalletClient, url, tokenHeader, tokenValue string) *Webhook {
return &Webhook{
URL: url,
TokenHeader: tokenHeader,
TokenValue: tokenValue,
Channel: make(chan Event, eventBufferLength),
client: client,
}
}

func (w *Webhook) Subscribe(ctx context.Context) ResponseError {
return w.client.AdminSubscribeWebhook(ctx, w.URL, w.TokenHeader, w.TokenValue)
}

func (w *Webhook) HTTPHandler() http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
var events []Event
if err := json.NewDecoder(r.Body).Decode(&events); err != nil {
http.Error(rw, err.Error(), http.StatusBadRequest)
return
}
fmt.Printf("Received: %v\n", events)
for _, event := range events {
select {
case w.Channel <- event:
// event sent
case <-r.Context().Done():
// context cancelled
return
case <-time.After(1 * time.Second):
// timeout, most probably the channel is full
// TODO: log this
}
}
rw.WriteHeader(http.StatusOK)
})
}

0 comments on commit 51f105c

Please sign in to comment.