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

WebLn Implementaion to Quick-pay #19

Open
wants to merge 29 commits into
base: version6
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
76dc2f6
WebLn lib added
SanjaySinghRajpoot Jun 3, 2022
bbbe787
WebLn lib added
SanjaySinghRajpoot Jun 3, 2022
c998175
BugFix: Invoice Error
SanjaySinghRajpoot Jun 8, 2022
ac32b0b
DC3~3~3~3~3~3~3~3~3~3~BBBBBBBBBAAAAAAAAAAAAAAABBAABBBBBBBBBBBBBBBMerg…
SanjaySinghRajpoot Jun 8, 2022
e48c2f5
WebLn lib added
SanjaySinghRajpoot Jun 3, 2022
8452a8a
BugFix: Invoice Error
SanjaySinghRajpoot Jun 8, 2022
5400870
Merge branch 'webln' of https://github.com/SanjaySinghRajpoot/RTL-Qui…
SanjaySinghRajpoot Jun 9, 2022
b936446
Merge branch 'version6' into pr/19
ShahanaFarooqui Jun 10, 2022
f7c34ce
connecting to browser
SanjaySinghRajpoot Jun 11, 2022
f16958e
error indent
SanjaySinghRajpoot Jun 14, 2022
6c5d729
WebLn lib added
SanjaySinghRajpoot Jun 3, 2022
812e7f5
BugFix: Invoice Error
SanjaySinghRajpoot Jun 8, 2022
812189c
Tiny updates
ShahanaFarooqui Jun 10, 2022
e48bca4
docs: Privacy Rules
SanjaySinghRajpoot Jun 5, 2022
2fd1eb6
Docs: password
SanjaySinghRajpoot Jun 7, 2022
a3d210c
connecting to browser
SanjaySinghRajpoot Jun 11, 2022
024a38e
error indent
SanjaySinghRajpoot Jun 14, 2022
7df4797
Merge branch 'webln' of https://github.com/SanjaySinghRajpoot/RTL-Qui…
SanjaySinghRajpoot Jun 14, 2022
60766be
Merge branch 'version6' into webln
SanjaySinghRajpoot Jun 14, 2022
5097a73
Merge branch 'version6' into webln
SanjaySinghRajpoot Jun 15, 2022
e845845
CSS file updated
SanjaySinghRajpoot Jun 15, 2022
2fc64bc
CSS link
SanjaySinghRajpoot Jun 15, 2022
4aa7f0c
WebLn provider
SanjaySinghRajpoot Jun 16, 2022
d3494f3
getInfo, sendPayment added
SanjaySinghRajpoot Jul 11, 2022
a079ae2
RPC call for getInfo
SanjaySinghRajpoot Jul 30, 2022
68a1319
feat: replacing RPC with REST WebLN
SanjaySinghRajpoot Aug 10, 2022
88f6a99
inject script and content script added
SanjaySinghRajpoot Aug 16, 2022
dea772e
added webln actions and CLN REST calls
SanjaySinghRajpoot Aug 21, 2022
aedf74a
ajax request
SanjaySinghRajpoot Aug 29, 2022
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
9,870 changes: 9,867 additions & 3 deletions dist/assets/themes/day/indigo.css

Large diffs are not rendered by default.

9,870 changes: 9,867 additions & 3 deletions dist/assets/themes/day/pink.css

Large diffs are not rendered by default.

9,870 changes: 9,867 additions & 3 deletions dist/assets/themes/day/purple.css

Large diffs are not rendered by default.

9,870 changes: 9,867 additions & 3 deletions dist/assets/themes/day/teal.css

Large diffs are not rendered by default.

8,886 changes: 8,884 additions & 2 deletions dist/assets/themes/day/yellow.css

Large diffs are not rendered by default.

9,866 changes: 9,863 additions & 3 deletions dist/assets/themes/night/indigo.css

Large diffs are not rendered by default.

9,866 changes: 9,863 additions & 3 deletions dist/assets/themes/night/pink.css

Large diffs are not rendered by default.

9,866 changes: 9,863 additions & 3 deletions dist/assets/themes/night/purple.css

Large diffs are not rendered by default.

9,866 changes: 9,863 additions & 3 deletions dist/assets/themes/night/teal.css

Large diffs are not rendered by default.

9,866 changes: 9,863 additions & 3 deletions dist/assets/themes/night/yellow.css

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions dist/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
"default_title": "RTL Quickpay",
"default_popup": "./index.html"
},
"web_accessible_resources": [
"webln-bundle.js"
],
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
Expand Down
2 changes: 1 addition & 1 deletion dist/pages/payment.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/scripts/content.js

Large diffs are not rendered by default.

Binary file modified packages/RTL-Quickpay-v0.0.6.zip
Binary file not shown.
72 changes: 72 additions & 0 deletions src/actions/webln_action.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import lightningPayReq from "bolt11";
import utils from "~/common/lib/utils";

export const getInfo = async (message, sender) => {
const connector = await state.getState().getConnector();
const info = await connector.getInfo();

return {
data: {
node: {
alias: info.data.alias,
pubkey: info.data.pubkey,
color: info.data.color,
},
},
};
};

export const makeInvoiceWithPrompt = async (message) => {
const amount = message.args.amount || message.args.defaultAmount;
const memo = message.args.memo || message.args.defaultMemo;
const amountEditable = !message.args.amount;
const memoEditable = !message.args.memo || message.args.memo === "";
// If amount is not defined yet, let the user generate an invoice with an amount field.
try {
const response = await utils.openPrompt({
origin: message.origin,
action: "makeInvoice",
args: {
amountEditable,
memoEditable,
invoiceAttributes: {
amount,
memo,
minimumAmount: message.args.minimumAmount,
maximumAmount: message.args.maximumAmount,
},
},
});
return response;
}
catch (e) {
return { error: e instanceof Error ? e.message : e };
}
};

async function payWithPrompt(message) {
try {
const response = await utils.openPrompt(Object.assign(Object.assign({}, message), { action: "confirmPayment" }));
return response;
}
catch (e) {
console.error("Payment cancelled", e);
if (e instanceof Error) {
return { error: e.message };
}
}
Comment on lines +47 to +57
Copy link

Choose a reason for hiding this comment

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

The payWithPrompt function is designed to handle user confirmation for payments. The error handling could be improved by providing a more detailed error message to the user, rather than just logging it to the console.

- console.error("Payment cancelled", e);
+ return { error: "Payment cancelled: " + (e instanceof Error ? e.message : e) };

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
async function payWithPrompt(message) {
try {
const response = await utils.openPrompt(Object.assign(Object.assign({}, message), { action: "confirmPayment" }));
return response;
}
catch (e) {
console.error("Payment cancelled", e);
if (e instanceof Error) {
return { error: e.message };
}
}
async function payWithPrompt(message) {
try {
const response = await utils.openPrompt(Object.assign(Object.assign({}, message), { action: "confirmPayment" }));
return response;
}
catch (e) {
return { error: "Payment cancelled: " + (e instanceof Error ? e.message : e) };
if (e instanceof Error) {
return { error: e.message };
}
}
}

}

export const signMessageOrPrompt = async (message) => {
const messageToSign = message.args.message;
if (typeof messageToSign !== "string") {
return {
error: "Message missing.",
};
}
return signWithPrompt(message);
};

export {
payWithPrompt,
}
9,870 changes: 9,867 additions & 3 deletions src/assets/themes/day/indigo.css

Large diffs are not rendered by default.

9,870 changes: 9,867 additions & 3 deletions src/assets/themes/day/pink.css

Large diffs are not rendered by default.

9,870 changes: 9,867 additions & 3 deletions src/assets/themes/day/purple.css

Large diffs are not rendered by default.

9,870 changes: 9,867 additions & 3 deletions src/assets/themes/day/teal.css

Large diffs are not rendered by default.

8,886 changes: 8,884 additions & 2 deletions src/assets/themes/day/yellow.css

Large diffs are not rendered by default.

9,866 changes: 9,863 additions & 3 deletions src/assets/themes/night/indigo.css

Large diffs are not rendered by default.

9,866 changes: 9,863 additions & 3 deletions src/assets/themes/night/pink.css

Large diffs are not rendered by default.

9,866 changes: 9,863 additions & 3 deletions src/assets/themes/night/purple.css

Large diffs are not rendered by default.

9,866 changes: 9,863 additions & 3 deletions src/assets/themes/night/teal.css

Large diffs are not rendered by default.

9,866 changes: 9,863 additions & 3 deletions src/assets/themes/night/yellow.css

Large diffs are not rendered by default.

132 changes: 132 additions & 0 deletions src/connectors/cln.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
const GetInfoResponse = require('./connectors.interface.js');
import utils from "../shared/utils";

class Cln{
init() {
return Promise.resolve();
}

unload() {
return Promise.resolve();
}

getInfo() {
return this.request("GET", "/v1/getinfo", undefined, {}).then((data) => {
return {
data: {
alias: data.alias,
pubkey: data.identity_pubkey,
color: data.color,
},
};
});
}

sendPayment(args) {
return this.request("POST", "/v1/channels/transactions", {
payment_request: args.paymentRequest,
}, {}).then((data) => {
if (data.payment_error) {
throw new Error(data.payment_error);
}
return {
data: {
preimage: utils.base64ToHex(data.payment_preimage),
paymentHash: utils.base64ToHex(data.payment_hash),
route: data.payment_route,
},
};
});
}

makeInvoice(args) {
return this.request("POST", "/v1/invoices", {
memo: args.memo,
value: args.amount,
}).then((data) => {
return {
data: {
paymentRequest: data.payment_request,
rHash: utils.base64ToHex(data.r_hash),
},
};
});
}

getAddress() {
return this.request("POST", "/v2/wallet/address/next", undefined, {});
}

async getInvoices() {
const data = await this.request("GET", "/v1/invoices", { reversed: true });
const invoices = data.invoices
.map((invoice, index) => {
const custom_records = invoice.htlcs[0] && invoice.htlcs[0].custom_records;
return {
custom_records,
id: `${invoice.payment_request}-${index}`,
memo: invoice.memo,
preimage: invoice.r_preimage,
settled: invoice.settled,
settleDate: parseInt(invoice.settle_date) * 1000,
totalAmount: invoice.value,
type: "received",
};
})
.reverse();
return {
data: {
invoices,
},
};
}

async request(method, path, args, defaultValues) {
const url = new URL(this.config.url);
url.pathname = `${url.pathname.replace(/\/$/, "")}${path}`;
let body = null;
const headers = new Headers();
headers.append("Accept", "application/json");
if (method === "POST") {
body = JSON.stringify(args);
headers.append("Content-Type", "application/json");
}
else if (args !== undefined) {
url.search = new URLSearchParams(args).toString();
}
if (this.config.macaroon) {
headers.append("Grpc-Metadata-macaroon", this.config.macaroon);
}
const res = await fetch(url.toString(), {
method,
headers,
body,
});
if (!res.ok) {
let errBody;
try {
errBody = await res.json();
// map it over for now.
if (errBody.message && !errBody.error) {
errBody.error = errBody.message;
delete errBody.message;
}
if (!errBody.error) {
throw new Error("Something went wrong");
}
}
catch (err) {
throw new Error(res.statusText);
}
console.error(errBody);
throw new Error(errBody.error);
}
let data = await res.json();
if (defaultValues) {
data = Object.assign(Object.assign({}, defaultValues), data);
}
return data;
}
}

export default Cln;
9 changes: 9 additions & 0 deletions src/connectors/connectors.interface.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const WebLNNode = {
alias: string,
pubkey: string,
color: string,
}

export const GetInfoResponse = {
data: WebLNNode,
};
3 changes: 3 additions & 0 deletions src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
"default_title": "RTL Quickpay",
"default_popup": "./index.html"
},
"web_accessible_resources": [
"webln-bundle.js"
],
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
Expand Down
21 changes: 10 additions & 11 deletions src/pages/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,30 @@ Error.prototype.initEvents = function () {

function onPageLoad() {
if (self.errorData) {
if(self.errorData.error) {
if (typeof self.errorData.error != 'string') {
if (self.errorData.error) {
if (typeof self.errorData.error != "string") {
self.errorData.error = JSON.stringify(self.errorData.error, null, 2);
}
$('#errorMsg').text(self.errorData.error);
$("#errorMsg").text(self.errorData.error);
}
if(self.errorData.message) {
$('#errorTitle').text(self.errorData.message);
if (self.errorData.message) {
$("#errorTitle").text(self.errorData.message);
}
}
}

pageContainer.keyup(function(event) {
pageContainer.keyup(function (event) {
event.preventDefault();
if(event.code == 'Enter') {
$('#backBtn').click();
if (event.code == "Enter") {
$("#backBtn").click();
}
});

$('#backBtn').click(function () {
$("#backBtn").click(function () {
loadModule({ load: self.loadedFrom, loadedFrom: CONSTANTS.MODULES.ERROR });
});

};

Error.prototype.render = function () {
pageContainer.html(errorHtml);
};
};
11 changes: 9 additions & 2 deletions src/pages/payment.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ let Payment = function (params) {
this.loadedFrom = params.loadedFrom;
};

const WebLNProvider = require('../webln/webln.js');

var weblnprovider = new WebLNProvider();

Payment.prototype.initEvents = function () {
"use strict";
var self = this;
Expand Down Expand Up @@ -47,7 +51,7 @@ Payment.prototype.initEvents = function () {
}

$('#sendPaymentBtn').click(function () {
$('#sendPaymentBtn').html(CONSTANTS.SPINNER_BTN);
$('#sendPaymentBtn').html(CONSTANTS.SPINNER_BTN);
let reqData = {};
let invoiceVal = $('#invoice').val();
let invoiceAmount = $('#invoiceAmount').val();
Expand Down Expand Up @@ -215,8 +219,11 @@ Payment.prototype.initEvents = function () {
$('#sendPaymentBtn').removeAttr('disabled');
}
}).catch(err => {
$("#paymentDetails").html(`<div class="alert alert-danger" role="alert">
<strong><h6 id="errorTitle">Invalid ID</h6></strong>
<span id="errorMsg">Please Enter Correct Invoice ID.</span>
</div>`);
$('#paymentDetails').html(createPaymentDetailsHTML('ERROR', selectNodeImplementation, err.responseJSON));
$('#paymentDetails').addClass('invalid-border');
$('#sendPaymentBtn').attr('disabled', true);
});
}
Expand Down
71 changes: 71 additions & 0 deletions src/scripts/background.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
var payReqForContext = '';
var browser = require('webextension-polyfill');
var injectScript = require('./injectScript.js')

// this is to open a dialog box with address, with the action of a left click
browser.runtime.onMessage.addListener((msg, sender) => {
if (msg.data && msg.source === 'CLICK' && msg.application === 'RTL') {
browser.windows.create({
Expand Down Expand Up @@ -36,4 +38,73 @@ browser.contextMenus.onClicked.addListener(
}
);

const weblnCalls = ["getInfo"];

// calls that can be executed when webln is not enabled for the current content page
const disabledCalls = ["enable"];

let isEnabled = false; // store if webln is enabled for this content page
let callActive = false; // store if a webln is currently active. Used to prevent multiple calls in parallel

async function init() {

injectScript(); // injects the webln object

// message listener to listen to inpage webln calls
// those calls get passed on to the background script
// (the inpage script can not do that directly, but only the inpage script can make webln available to the page)
window.addEventListener("message", (ev) => {
// Only accept messages from the current window
if (ev.source !== window) {
return;
}
if (ev.data && ev.data.application === "RTL" && !ev.data.response) {
// if a call is active we ignore the request
if (callActive) {
console.error("WebLN call already executing");
return;
}
// limit the calls that can be made from webln
// only listed calls can be executed
// if not enabled only enable can be called.
const availableCalls = isEnabled ? weblnCalls : disabledCalls;
if (!availableCalls.includes(ev.data.action)) {
console.error("Function not available. Is the provider enabled?");
return;
}

const messageWithOrigin = {
action: `webln/${ev.data.action}`, // every webln call must be scoped under `webln/` we do this to indicate that those actions are callable from the websites
args: ev.data.args,
application: "RTL",
public: true, // indicate that this is a public call from the content script
prompt: true,
origin: getOriginData(),
};
const replyFunction = (response) => {
callActive = false; // reset call is active
// if it is the enable call we store if webln is enabled for this content script
if (ev.data.action === "enable") {
isEnabled = response.data?.enabled;
}
window.postMessage(
{
application: "RTL",
response: true,
data: response,
},
"*" // TODO use origin
);
};
callActive = true;
return browser.runtime
.sendMessage(messageWithOrigin)
.then(replyFunction)
.catch(replyFunction);
}
});
}
Comment on lines +41 to +106
Copy link

Choose a reason for hiding this comment

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

The background.js file sets up the necessary infrastructure for handling WebLN calls. The init function and the message listener are correctly implemented to handle and process these calls. The use of isEnabled and callActive flags to manage the state is a good practice. Ensure that the TODO to use the origin in window.postMessage is addressed for security reasons.

- }, "*" // TODO use origin
+ }, window.location.origin)

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const weblnCalls = ["getInfo"];
// calls that can be executed when webln is not enabled for the current content page
const disabledCalls = ["enable"];
let isEnabled = false; // store if webln is enabled for this content page
let callActive = false; // store if a webln is currently active. Used to prevent multiple calls in parallel
async function init() {
injectScript(); // injects the webln object
// message listener to listen to inpage webln calls
// those calls get passed on to the background script
// (the inpage script can not do that directly, but only the inpage script can make webln available to the page)
window.addEventListener("message", (ev) => {
// Only accept messages from the current window
if (ev.source !== window) {
return;
}
if (ev.data && ev.data.application === "RTL" && !ev.data.response) {
// if a call is active we ignore the request
if (callActive) {
console.error("WebLN call already executing");
return;
}
// limit the calls that can be made from webln
// only listed calls can be executed
// if not enabled only enable can be called.
const availableCalls = isEnabled ? weblnCalls : disabledCalls;
if (!availableCalls.includes(ev.data.action)) {
console.error("Function not available. Is the provider enabled?");
return;
}
const messageWithOrigin = {
action: `webln/${ev.data.action}`, // every webln call must be scoped under `webln/` we do this to indicate that those actions are callable from the websites
args: ev.data.args,
application: "RTL",
public: true, // indicate that this is a public call from the content script
prompt: true,
origin: getOriginData(),
};
const replyFunction = (response) => {
callActive = false; // reset call is active
// if it is the enable call we store if webln is enabled for this content script
if (ev.data.action === "enable") {
isEnabled = response.data?.enabled;
}
window.postMessage(
{
application: "RTL",
response: true,
data: response,
},
"*" // TODO use origin
);
};
callActive = true;
return browser.runtime
.sendMessage(messageWithOrigin)
.then(replyFunction)
.catch(replyFunction);
}
});
}
const weblnCalls = ["getInfo"];
// calls that can be executed when webln is not enabled for the current content page
const disabledCalls = ["enable"];
let isEnabled = false; // store if webln is enabled for this content page
let callActive = false; // store if a webln is currently active. Used to prevent multiple calls in parallel
async function init() {
injectScript(); // injects the webln object
// message listener to listen to inpage webln calls
// those calls get passed on to the background script
// (the inpage script can not do that directly, but only the inpage script can make webln available to the page)
window.addEventListener("message", (ev) => {
// Only accept messages from the current window
if (ev.source !== window) {
return;
}
if (ev.data && ev.data.application === "RTL" && !ev.data.response) {
// if a call is active we ignore the request
if (callActive) {
console.error("WebLN call already executing");
return;
}
// limit the calls that can be made from webln
// only listed calls can be executed
// if not enabled only enable can be called.
const availableCalls = isEnabled ? weblnCalls : disabledCalls;
if (!availableCalls.includes(ev.data.action)) {
console.error("Function not available. Is the provider enabled?");
return;
}
const messageWithOrigin = {
action: `webln/${ev.data.action}`, // every webln call must be scoped under `webln/` we do this to indicate that those actions are callable from the websites
args: ev.data.args,
application: "RTL",
public: true, // indicate that this is a public call from the content script
prompt: true,
origin: getOriginData(),
};
const replyFunction = (response) => {
callActive = false; // reset call is active
// if it is the enable call we store if webln is enabled for this content script
if (ev.data.action === "enable") {
isEnabled = response.data?.enabled;
}
window.postMessage(
{
application: "RTL",
response: true,
data: response,
},
window.location.origin)
};
callActive = true;
return browser.runtime
.sendMessage(messageWithOrigin)
.then(replyFunction)
.catch(replyFunction);
}
});
}


init();

createContextMenu();
Loading