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

feat: implement webbtc #1561

Merged
merged 2 commits into from
Oct 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src/extension/content-script/onend.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ async function init() {
}
});

// message listener to listen to inpage webln calls
// message listener to listen to inpage webln/webbtc 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) => {
Expand Down
2 changes: 2 additions & 0 deletions src/extension/inpage-script/webln.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import WebBTCProvider from "../ln/webbtc";
import WebLNProvider from "../ln/webln";

if (document) {
window.webln = new WebLNProvider();
window.webbtc = new WebBTCProvider();
}
145 changes: 145 additions & 0 deletions src/extension/ln/webbtc/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
type RequestInvoiceArgs = {
amount?: string | number;
defaultAmount?: string | number;
minimumAmount?: string | number;
maximumAmount?: string | number;
defaultMemo?: string;
};

export default class WebBTCProvider {
enabled: boolean;
isEnabled: boolean;
executing: boolean;

constructor() {
this.enabled = false;
this.isEnabled = false;
this.executing = false;
}

enable() {
if (this.enabled) {
return Promise.resolve({ enabled: true });
}
return this.execute("enable").then((result) => {
if (typeof result.enabled === "boolean") {
this.enabled = result.enabled;
this.isEnabled = result.enabled;
}
return result;
});
}

getInfo() {
if (!this.enabled) {
throw new Error("Provider must be enabled before calling getInfo");
}
return {
version: "stable",
supports: ["lightning"],
methods: [
"enable",
"getInfo",
"signMessage",
"verifyMessage",
"makeInvoice",
"sendPayment",
"keysend",
],
};
}

signMessage(message: string) {
if (!this.enabled) {
throw new Error("Provider must be enabled before calling signMessage");
}

return this.execute("signMessageOrPrompt", { message });
}

verifyMessage(signature: string, message: string) {
if (!this.enabled) {
throw new Error("Provider must be enabled before calling verifyMessage");
}
throw new Error("Alby does not support `verifyMessage`");
}

makeInvoice(args: string | number | RequestInvoiceArgs) {
if (!this.enabled) {
throw new Error("Provider must be enabled before calling makeInvoice");
}
if (typeof args !== "object") {
args = { amount: args };
}

return this.execute("makeInvoice", args);
}

sendPayment(paymentRequest: string) {
if (!this.enabled) {
throw new Error("Provider must be enabled before calling sendPayment");
}
return this.execute("sendPaymentOrPrompt", { paymentRequest });
}

sendTransaction(address: string, amount: string) {
if (!this.enabled) {
throw new Error(
"Provider must be enabled before calling sendTransaction"
);
}
throw new Error("Alby does not support `sendTransaction`");
}

getAddress(index: number, num: number, change: boolean) {
if (!this.enabled) {
throw new Error("Provider must be enabled before calling getAddress");
}
throw new Error("Alby does not support `getAddress`");
}

// NOTE: new call `action`s must be specified also in the content script
execute(
action: string,
args?: Record<string, unknown>
): Promise<Record<string, unknown>> {
return new Promise((resolve, reject) => {
// post the request to the content script. from there it gets passed to the background script and back
// in page script can not directly connect to the background script
window.postMessage(
{
application: "LBE",
prompt: true,
action: `webln/${action}`,
scope: "webbtc",
args,
},
"*" // TODO use origin
);

function handleWindowMessage(messageEvent: MessageEvent) {
// check if it is a relevant message
// there are some other events happening
if (
!messageEvent.data ||
!messageEvent.data.response ||
messageEvent.data.application !== "LBE"
) {
return;
}
if (messageEvent.data.data.error) {
reject(new Error(messageEvent.data.data.error));
} else {
// 1. data: the message data
// 2. data: the data passed as data to the message
// 3. data: the actual response data
resolve(messageEvent.data.data.data);
}
// For some reason must happen only at the end of this function
window.removeEventListener("message", handleWindowMessage);
}

window.addEventListener("message", handleWindowMessage);
});
}
}