diff --git a/src/extension/content-script/onend.js b/src/extension/content-script/onend.js index b59acd422b..90ba3d5cfa 100644 --- a/src/extension/content-script/onend.js +++ b/src/extension/content-script/onend.js @@ -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) => { diff --git a/src/extension/inpage-script/webln.js b/src/extension/inpage-script/webln.js index 9adc288270..64589bb9c2 100644 --- a/src/extension/inpage-script/webln.js +++ b/src/extension/inpage-script/webln.js @@ -1,5 +1,7 @@ +import WebBTCProvider from "../ln/webbtc"; import WebLNProvider from "../ln/webln"; if (document) { window.webln = new WebLNProvider(); + window.webbtc = new WebBTCProvider(); } diff --git a/src/extension/ln/webbtc/index.ts b/src/extension/ln/webbtc/index.ts new file mode 100644 index 0000000000..095a336c42 --- /dev/null +++ b/src/extension/ln/webbtc/index.ts @@ -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 + ): Promise> { + 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); + }); + } +}