added frontend

This commit is contained in:
michael1011 2018-03-25 17:45:42 +02:00
parent f7e60168b4
commit 980727629e
No known key found for this signature in database
GPG Key ID: 84D249BA71685D46
7 changed files with 269 additions and 69 deletions

View File

@ -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)
}

View File

@ -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

View File

@ -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,

111
frontend/lightningTip.css Normal file
View File

@ -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);
}
}

View File

@ -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>

69
frontend/lightningTip.js Normal file
View File

@ -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;
}

View File

@ -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,
}))
}