added frontend
This commit is contained in:
parent
f7e60168b4
commit
980727629e
|
@ -3,6 +3,6 @@ package backends
|
|||
type Backend interface {
|
||||
Connect() error
|
||||
|
||||
// Value in satoshis and expiry in seconds
|
||||
GetInvoice(description string, value int64, expiry int64) (invoice string, err error)
|
||||
// Amount in satoshis and expiry in seconds
|
||||
GetInvoice(description string, amount int64, expiry int64) (invoice string, err error)
|
||||
}
|
||||
|
|
|
@ -66,12 +66,22 @@ func getMacaroon(macaroonFile string) (macaroon metadata.MD, err error) {
|
|||
return macaroon, err
|
||||
}
|
||||
|
||||
func (lnd *LND) GetInvoice(description string, value int64, expiry int64) (invoice string, err error) {
|
||||
response, err := lnd.client.AddInvoice(lnd.ctx, &lnrpc.Invoice{
|
||||
Memo: description,
|
||||
Value: value,
|
||||
Expiry: expiry,
|
||||
})
|
||||
func (lnd *LND) GetInvoice(message string, amount int64, expiry int64) (invoice string, err error) {
|
||||
var response *lnrpc.AddInvoiceResponse
|
||||
|
||||
if message != "" {
|
||||
response, err = lnd.client.AddInvoice(lnd.ctx, &lnrpc.Invoice{
|
||||
Memo: message,
|
||||
Value: amount,
|
||||
Expiry: expiry,
|
||||
})
|
||||
|
||||
} else {
|
||||
response, err = lnd.client.AddInvoice(lnd.ctx, &lnrpc.Invoice{
|
||||
Value: amount,
|
||||
Expiry: expiry,
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
14
config.go
14
config.go
|
@ -10,8 +10,6 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// TODO: add option to show URI of Lightning node
|
||||
|
||||
const (
|
||||
defaultConfigFile = "lightningTip.conf"
|
||||
|
||||
|
@ -20,9 +18,7 @@ const (
|
|||
|
||||
defaultRESTHost = "localhost:8081"
|
||||
|
||||
defaultTipMessage = "A generous tip"
|
||||
defaultTipExpiry = 3600
|
||||
defaultTipValue = 100
|
||||
defaultTipExpiry = 3600
|
||||
|
||||
defaultLndRPCHost = "localhost:10009"
|
||||
defaultLndCertFile = "tls.cert"
|
||||
|
@ -37,9 +33,7 @@ type config struct {
|
|||
|
||||
RESTHost string `long:"resthost" Description:"Host for the rest interface of LightningTip"`
|
||||
|
||||
TipMessage string `long:"tipmessage" Description:"Message embedded in the payment request"`
|
||||
TipExpiry int64 `long:"tipexpiry" Description:"Expiry time in seconds"`
|
||||
DefaultTipValue int64 `long:"defaulttipvalue" Description:"The default value of a tip in satoshis"`
|
||||
TipExpiry int64 `long:"tipexpiry" Description:"Expiry time in seconds"`
|
||||
|
||||
LND *backends.LND `group:"LND" namespace:"lnd"`
|
||||
}
|
||||
|
@ -59,9 +53,7 @@ func initConfig() {
|
|||
|
||||
RESTHost: defaultRESTHost,
|
||||
|
||||
TipMessage: defaultTipMessage,
|
||||
TipExpiry: defaultTipExpiry,
|
||||
DefaultTipValue: defaultTipValue,
|
||||
TipExpiry: defaultTipExpiry,
|
||||
|
||||
LND: &backends.LND{
|
||||
RPCHost: defaultLndRPCHost,
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
#lightningTip {
|
||||
width: 12em;
|
||||
|
||||
padding: 1em;
|
||||
|
||||
background-color: #212121;
|
||||
|
||||
border-radius: 4px;
|
||||
|
||||
color: #F5F5F5;
|
||||
|
||||
font-size: 20px;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#lightningTipLogo {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.6em;
|
||||
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
#lightningTipInputs {
|
||||
margin-top: 0.8em;
|
||||
}
|
||||
|
||||
#lightningTipMessage {
|
||||
min-height: 55px;
|
||||
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
|
||||
margin-top: 0.5em;
|
||||
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.lightningTipInput {
|
||||
width: 100%;
|
||||
|
||||
display: inline-block;
|
||||
|
||||
padding: 6px 10px;
|
||||
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
|
||||
font-size: 15px;
|
||||
|
||||
color: #212121;
|
||||
|
||||
background-color: #F5F5F5;
|
||||
|
||||
outline: none;
|
||||
resize: none;
|
||||
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
#lightningTipGetInvoice {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 0.3em;
|
||||
|
||||
padding: 0.4em 1em;
|
||||
|
||||
font-size: 17px;
|
||||
|
||||
color: #212121;
|
||||
|
||||
background-color: #FFC83D;
|
||||
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#lightningTipError {
|
||||
font-size: 17px;
|
||||
|
||||
color: #F44336;
|
||||
}
|
||||
|
||||
#lightningTipInvoice {
|
||||
margin-top: 0.8em;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
|
||||
display: inline-block;
|
||||
vertical-align: center;
|
||||
|
||||
border: 3px solid #F5F5F5;
|
||||
border-top: 3px solid #212121;
|
||||
border-radius: 50%;
|
||||
|
||||
animation: spin 1.5s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<link rel="stylesheet" href="lightningTip.css">
|
||||
|
||||
<script src="lightningTip.js"></script>
|
||||
<!-- TODO: add qr code <script src="https://cdn.rawgit.com/davidshimjs/qrcodejs/gh-pages/qrcode.min.js"></script>-->
|
||||
<!-- TODO: add option to open in wallet lightning:<invoice> -->
|
||||
</head>
|
||||
|
||||
<div id="lightningTip">
|
||||
<p id="lightningTipLogo">⚡</p>
|
||||
<a>Send a tip via Lightning</a>
|
||||
|
||||
<div id="lightningTipInputs">
|
||||
<input type="text" class="lightningTipInput" id="lightningTipAmount" placeholder="Amount in satoshis">
|
||||
<br>
|
||||
<textarea class="lightningTipInput" id="lightningTipMessage" placeholder="A message you want to add" oninput="resizeInput(this)"></textarea>
|
||||
|
||||
<button id="lightningTipGetInvoice" onclick="getInvoice()">Get Invoice</button>
|
||||
|
||||
<div>
|
||||
<a id="lightningTipError"></a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,69 @@
|
|||
function resizeInput(element) {
|
||||
element.style.height = "auto";
|
||||
element.style.height = (element.scrollHeight) + "px";
|
||||
}
|
||||
|
||||
|
||||
function getInvoice() {
|
||||
var tipValue = document.getElementById("lightningTipAmount");
|
||||
|
||||
if (tipValue.value !== "") {
|
||||
if (!isNaN(tipValue.value)) {
|
||||
var data = JSON.stringify({"Amount": parseInt(tipValue.value), "Message": document.getElementById("lightningTipMessage").value});
|
||||
|
||||
var request = new XMLHttpRequest();
|
||||
|
||||
request.onreadystatechange = function () {
|
||||
if (request.readyState === 4) {
|
||||
var json = JSON.parse(request.responseText);
|
||||
|
||||
if (request.status === 200) {
|
||||
console.log("Got invoice: " + json.Invoice);
|
||||
console.log("Invoice expires in: " + json.Expiry);
|
||||
|
||||
var wrapper = document.getElementById("lightningTip");
|
||||
|
||||
// TODO: timer until expiry
|
||||
wrapper.innerHTML = "<a>Your invoice</a>";
|
||||
wrapper.innerHTML += "<textarea class='lightningTipInput' id='lightningTipInvoice' readonly>" + json.Invoice + "</textarea>";
|
||||
|
||||
resizeInput(document.getElementById("lightningTipInvoice"))
|
||||
|
||||
} else {
|
||||
showErrorMessage(json.Error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// TODO: proper url handling window.location.protocol + window.location.hostname + ":8081/getinvoice"
|
||||
request.open("POST", "http://localhost:8081/getinvoice", true);
|
||||
request.send(data);
|
||||
|
||||
var button = document.getElementById("lightningTipGetInvoice");
|
||||
|
||||
button.style.height = button.clientHeight + "px";
|
||||
button.style.width = button.clientWidth + "px";
|
||||
|
||||
button.innerHTML = "<div class='spinner'></div>";
|
||||
|
||||
} else {
|
||||
showErrorMessage("Tip amount must be a number");
|
||||
}
|
||||
|
||||
} else {
|
||||
showErrorMessage("No tip amount set");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function showErrorMessage(message) {
|
||||
console.error(message);
|
||||
|
||||
var error = document.getElementById("lightningTipError");
|
||||
|
||||
error.parentElement.style.marginTop = "0.5em";
|
||||
|
||||
error.innerHTML = message;
|
||||
}
|
|
@ -9,24 +9,21 @@ import (
|
|||
"strconv"
|
||||
)
|
||||
|
||||
type getInvoiceRequest struct {
|
||||
Value int64
|
||||
Message string
|
||||
}
|
||||
|
||||
type invoiceResponse struct {
|
||||
Invoice string
|
||||
Error string
|
||||
Expiry int64
|
||||
}
|
||||
|
||||
type tipValueResponse struct {
|
||||
TipValue int64
|
||||
type invoiceRequest struct {
|
||||
Amount int64
|
||||
Message string
|
||||
}
|
||||
|
||||
type errorResponse struct {
|
||||
Error string
|
||||
}
|
||||
|
||||
// TODO: add option to show URI of Lightning node
|
||||
func main() {
|
||||
initLog()
|
||||
|
||||
|
@ -37,11 +34,11 @@ func main() {
|
|||
if err == nil {
|
||||
http.HandleFunc("/", notFoundHandler)
|
||||
http.HandleFunc("/getinvoice", getInvoiceHandler)
|
||||
http.HandleFunc("/defaulttipvalue", defaultTipValueHandler)
|
||||
|
||||
log.Info("Subscribing to invoices")
|
||||
|
||||
go func() {
|
||||
// TODO: let clients listen if their invoice was paid (eventsource)
|
||||
err = cfg.LND.SubscribeInvoices()
|
||||
|
||||
if err != nil {
|
||||
|
@ -72,66 +69,59 @@ func main() {
|
|||
}
|
||||
|
||||
func getInvoiceHandler(writer http.ResponseWriter, request *http.Request) {
|
||||
var errorMessage string
|
||||
|
||||
tipValue := cfg.DefaultTipValue
|
||||
tipMessage := cfg.TipMessage
|
||||
errorMessage := "Could not parse values from request"
|
||||
|
||||
if request.Method == http.MethodPost {
|
||||
var body getInvoiceRequest
|
||||
var body invoiceRequest
|
||||
|
||||
data, _ := ioutil.ReadAll(request.Body)
|
||||
|
||||
err := json.Unmarshal(data, &body)
|
||||
|
||||
if err == nil {
|
||||
if body.Value != 0 {
|
||||
tipValue = body.Value
|
||||
if body.Amount != 0 {
|
||||
invoice, err := backend.GetInvoice(body.Message, body.Amount, cfg.TipExpiry)
|
||||
|
||||
if err == nil {
|
||||
logMessage := "Created invoice with amount of " + strconv.FormatInt(body.Amount, 10) + " satoshis"
|
||||
|
||||
if body.Message != "" {
|
||||
logMessage += " with message \"" + body.Message + "\""
|
||||
}
|
||||
|
||||
log.Info(logMessage)
|
||||
|
||||
writer.Write(marshalJson(invoiceResponse{
|
||||
Invoice: invoice,
|
||||
Expiry: cfg.TipExpiry,
|
||||
}))
|
||||
|
||||
return
|
||||
|
||||
} else {
|
||||
errorMessage = "Failed to create invoice"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if body.Message != "" {
|
||||
tipMessage = body.Message
|
||||
}
|
||||
|
||||
} else {
|
||||
errorMessage = "Could not parse values from request"
|
||||
|
||||
log.Warning(errorMessage)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
invoice, err := backend.GetInvoice(tipMessage, tipValue, cfg.TipExpiry)
|
||||
log.Error(errorMessage)
|
||||
|
||||
if err == nil {
|
||||
log.Info("Created invoice with value of " + strconv.FormatInt(tipValue, 10) + " satoshis")
|
||||
|
||||
writer.Write(marshalJson(invoiceResponse{
|
||||
Invoice: invoice,
|
||||
Error: errorMessage,
|
||||
}))
|
||||
|
||||
} else {
|
||||
errorMessage := "Failed to create invoice"
|
||||
|
||||
log.Error(errorMessage + ": " + fmt.Sprint(err))
|
||||
|
||||
writer.Write(marshalJson(errorResponse{
|
||||
Error: errorMessage,
|
||||
}))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func defaultTipValueHandler(writer http.ResponseWriter, request *http.Request) {
|
||||
writer.Write(marshalJson(tipValueResponse{
|
||||
TipValue: cfg.DefaultTipValue,
|
||||
}))
|
||||
writeError(writer, errorMessage)
|
||||
}
|
||||
|
||||
func notFoundHandler(writer http.ResponseWriter, request *http.Request) {
|
||||
writeError(writer, "Not found")
|
||||
}
|
||||
|
||||
func writeError(writer http.ResponseWriter, message string) {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
|
||||
writer.Write(marshalJson(errorResponse{
|
||||
Error: "Not found",
|
||||
Error: message,
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue