From a4ad46471098bd29a6f079e97981f1e68bbfa21f Mon Sep 17 00:00:00 2001 From: michael1011 Date: Mon, 26 Mar 2018 23:54:12 +0200 Subject: [PATCH] added EventSource to show a thank you message --- backends/backend.go | 5 ++++ backends/lnd.go | 12 +++++--- frontend/lightningTip.css | 6 ++++ frontend/lightningTip.html | 1 + frontend/lightningTip.js | 31 ++++++++++++++++++-- lightningtip.go | 60 ++++++++++++++++++++++++++++++++++++-- 6 files changed, 107 insertions(+), 8 deletions(-) diff --git a/backends/backend.go b/backends/backend.go index 4f650fd..a672dc2 100644 --- a/backends/backend.go +++ b/backends/backend.go @@ -1,5 +1,10 @@ package backends +import "github.com/donovanhide/eventsource" + +// For callbacks when an invoice gets settled +type PublishInvoiceSettled func(invoice string, eventSrv *eventsource.Server) + type Backend interface { Connect() error diff --git a/backends/lnd.go b/backends/lnd.go index 8a932f4..61657e3 100644 --- a/backends/lnd.go +++ b/backends/lnd.go @@ -4,7 +4,7 @@ import ( "context" "encoding/hex" "errors" - "fmt" + "github.com/donovanhide/eventsource" "github.com/lightningnetwork/lnd/lnrpc" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -90,7 +90,7 @@ func (lnd *LND) GetInvoice(message string, amount int64, expiry int64) (invoice return response.PaymentRequest, err } -func (lnd *LND) SubscribeInvoices() error { +func (lnd *LND) SubscribeInvoices(callback PublishInvoiceSettled, eventSrv *eventsource.Server) error { stream, err := lnd.client.SubscribeInvoices(lnd.ctx, &lnrpc.InvoiceSubscription{}) if err != nil { @@ -101,7 +101,7 @@ func (lnd *LND) SubscribeInvoices() error { go func() { for { - in, streamErr := stream.Recv() + invoice, streamErr := stream.Recv() if streamErr == io.EOF { err = errors.New("lost connection to LND gRPC") @@ -119,8 +119,12 @@ func (lnd *LND) SubscribeInvoices() error { return } - fmt.Println(in) + if invoice.Settled { + callback(invoice.PaymentRequest, eventSrv) + } + } + }() <-wait diff --git a/frontend/lightningTip.css b/frontend/lightningTip.css index 60f6594..b12cef2 100644 --- a/frontend/lightningTip.css +++ b/frontend/lightningTip.css @@ -115,6 +115,12 @@ float: right; } +#lightningTipThankYou { + margin-bottom: 0.2em; + + display: block; +} + .spinner { width: 12px; height: 12px; diff --git a/frontend/lightningTip.html b/frontend/lightningTip.html index dd00806..88ee4bc 100644 --- a/frontend/lightningTip.html +++ b/frontend/lightningTip.html @@ -5,6 +5,7 @@ +
diff --git a/frontend/lightningTip.js b/frontend/lightningTip.js index 6dc7103..f3f7f60 100644 --- a/frontend/lightningTip.js +++ b/frontend/lightningTip.js @@ -11,7 +11,10 @@ var qrCode; var defaultGetInvoice; -// TODO: listen to eventsource and show tank you when invoice settled +// TODO: show error when invoice expires +// TODO: maybe don't show full invoice +// TODO: proper url handling window.location.protocol + window.location.hostname + ":8081/getinvoice" +// TODO: show price in dollar? function getInvoice() { if (running === false) { running = true; @@ -34,6 +37,16 @@ function getInvoice() { console.log("Got invoice: " + json.Invoice); console.log("Invoice expires in: " + json.Expiry); + var hash = sha256(json.Invoice); + + console.log("Got hash of invoice: " + hash); + + // TODO: find alternative for Edge and IE + console.log("Starting listening for invoice to get settled"); + + listenInvoiceSettled(hash); + + // Update UI invoice = json.Invoice; var wrapper = document.getElementById("lightningTip"); @@ -65,6 +78,8 @@ function getInvoice() { } } catch (exception) { + console.error(exception); + showErrorMessage("Failed to reach backend"); } @@ -72,7 +87,6 @@ function getInvoice() { }; - // TODO: proper url handling window.location.protocol + window.location.hostname + ":8081/getinvoice" request.open("POST", "http://localhost:8081/getinvoice", true); request.send(data); @@ -99,6 +113,19 @@ function getInvoice() { } +function listenInvoiceSettled(hash) { + var eventSrc = new EventSource("http://localhost:8081/eventsource"); + + eventSrc.onmessage = function (event) { + if (event.data === hash) { + var wrapper = document.getElementById("lightningTip"); + + wrapper.innerHTML = "

"; + wrapper.innerHTML += "Thank you for your tip!"; + } + }; +} + function starTimer(duration, element) { showTimer(duration, element); diff --git a/lightningtip.go b/lightningtip.go index b2999a5..fe9b689 100644 --- a/lightningtip.go +++ b/lightningtip.go @@ -1,14 +1,35 @@ package main import ( + "crypto/sha256" + "encoding/hex" "encoding/json" "fmt" + "github.com/donovanhide/eventsource" "io/ioutil" "net/http" "os" "strconv" + "time" ) +const eventChannel = "invoiceSettled" + +var eventSrv *eventsource.Server + +var pendingInvoices []PendingInvoice + +type PendingInvoice struct { + Invoice string + Hash string + Expiry time.Time +} + +// To use the pendingInvoice type as event for the EventSource stream +func (pending PendingInvoice) Id() string { return "" } +func (pending PendingInvoice) Event() string { return "" } +func (pending PendingInvoice) Data() string { return pending.Hash } + type invoiceResponse struct { Invoice string Expiry int64 @@ -32,14 +53,18 @@ func main() { err := backend.Connect() if err == nil { + log.Info("Starting EventSource stream") + + eventSrv = eventsource.NewServer() + http.HandleFunc("/", notFoundHandler) http.HandleFunc("/getinvoice", getInvoiceHandler) + http.HandleFunc("/eventsource", eventSrv.Handler(eventChannel)) log.Info("Subscribing to invoices") go func() { - // TODO: let clients listen if their invoice was paid (eventsource) - err = cfg.LND.SubscribeInvoices() + err = cfg.LND.SubscribeInvoices(publishInvoiceSettled, eventSrv) if err != nil { log.Error("Failed to subscribe to invoices: " + fmt.Sprint(err)) @@ -68,6 +93,23 @@ func main() { } +// Callbacks when an invoice gets settled +func publishInvoiceSettled(invoice string, eventSrv *eventsource.Server) { + for index, pending := range pendingInvoices { + if pending.Invoice == invoice { + log.Info("Invoice settled: " + invoice) + + eventSrv.Publish([]string{eventChannel}, pending) + + pendingInvoices = append(pendingInvoices[:index], pendingInvoices[index+1:]...) + + break + } + + } + +} + func getInvoiceHandler(writer http.ResponseWriter, request *http.Request) { errorMessage := "Could not parse values from request" @@ -89,8 +131,22 @@ func getInvoiceHandler(writer http.ResponseWriter, request *http.Request) { logMessage += " with message \"" + body.Message + "\"" } + sha := sha256.New() + sha.Write([]byte(invoice)) + + hash := hex.EncodeToString(sha.Sum(nil)) + + expiryDuration := time.Duration(cfg.TipExpiry) * time.Second + log.Info(logMessage) + // TODO: check every minute or so if expired + pendingInvoices = append(pendingInvoices, PendingInvoice{ + Invoice: invoice, + Hash: hash, + Expiry: time.Now().Add(expiryDuration), + }) + writer.Write(marshalJson(invoiceResponse{ Invoice: invoice, Expiry: cfg.TipExpiry,