diff --git a/src/app/screens/Nostr/ConfirmSignMessage.tsx b/src/app/screens/Nostr/ConfirmSignMessage.tsx index 35a6fd1b96..b93acf499c 100644 --- a/src/app/screens/Nostr/ConfirmSignMessage.tsx +++ b/src/app/screens/Nostr/ConfirmSignMessage.tsx @@ -13,7 +13,7 @@ import ScreenHeader from "~/app/components/ScreenHeader"; import { useNavigationState } from "~/app/hooks/useNavigationState"; import { USER_REJECTED_ERROR } from "~/common/constants"; import msg from "~/common/lib/msg"; -import { Event } from "~/extension/ln/nostr/types"; +import { Event } from "~/extension/providers/nostr/types"; import type { OriginData } from "~/types"; function ConfirmSignMessage() { diff --git a/src/extension/background-script/actions/nostr/helpers.ts b/src/extension/background-script/actions/nostr/helpers.ts index e326a35dca..7a811cd770 100644 --- a/src/extension/background-script/actions/nostr/helpers.ts +++ b/src/extension/background-script/actions/nostr/helpers.ts @@ -3,7 +3,7 @@ import Hex from "crypto-js/enc-hex"; import sha256 from "crypto-js/sha256"; import db from "~/extension/background-script/db"; import state from "~/extension/background-script/state"; -import { Event } from "~/extension/ln/nostr/types"; +import { Event } from "~/extension/providers/nostr/types"; export async function hasPermissionFor(method: string, host: string) { if (!host) { diff --git a/src/extension/background-script/nostr/index.ts b/src/extension/background-script/nostr/index.ts index afca6912f4..897991b73e 100644 --- a/src/extension/background-script/nostr/index.ts +++ b/src/extension/background-script/nostr/index.ts @@ -5,7 +5,7 @@ import { AES } from "crypto-js"; import Base64 from "crypto-js/enc-base64"; import Hex from "crypto-js/enc-hex"; import Utf8 from "crypto-js/enc-utf8"; -import { Event } from "~/extension/ln/nostr/types"; +import { Event } from "~/extension/providers/nostr/types"; import { getEventHash, signEvent } from "../actions/nostr/helpers"; diff --git a/src/extension/content-script/onend.js b/src/extension/content-script/onend.js index d196a75d27..7a0dbc7815 100644 --- a/src/extension/content-script/onend.js +++ b/src/extension/content-script/onend.js @@ -55,14 +55,15 @@ async function init() { if (ev.data && !ev.data.response) { // if an enable call railed we ignore the request to prevent spamming the user with prompts if (isRejected) { - console.error( - "Enable had failed. Rejecting further WebLN calls until the next reload" - ); + postMessage(ev, { + error: + "webln.enable() failed (rejecting further window.webln calls until the next reload)", + }); return; } // if a call is active we ignore the request if (callActive) { - console.error("WebLN call already executing"); + postMessage(ev, { error: "window.webln call already executing" }); return; } // limit the calls that can be made from webln @@ -96,16 +97,7 @@ async function init() { isRejected = true; } } - window.postMessage( - { - application: "LBE", - response: true, - data: response, - //action: ev.data.action, - scope: "webln", - }, - "*" // TODO use origin - ); + postMessage(ev, response); }; callActive = true; return browser.runtime @@ -118,4 +110,17 @@ async function init() { init(); +function postMessage(ev, response) { + window.postMessage( + { + id: ev.data.id, + application: "LBE", + response: true, + data: response, + scope: "webln", + }, + "*" + ); +} + export {}; diff --git a/src/extension/content-script/onendnostr.js b/src/extension/content-script/onendnostr.js index 3cbe842ae9..e0d6ad44c5 100644 --- a/src/extension/content-script/onendnostr.js +++ b/src/extension/content-script/onendnostr.js @@ -27,7 +27,7 @@ async function init() { return; } - // message listener to listen to inpage webln calls + // message listener to listen to inpage nostr 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) => { @@ -43,14 +43,16 @@ async function init() { if (ev.data && !ev.data.response) { // if an enable call railed we ignore the request to prevent spamming the user with prompts if (isRejected) { - console.error( - "Enable had failed. Rejecting further WebLN calls until the next reload" - ); + postMessage(ev, { + error: + "window.nostr call cancelled (rejecting further window.nostr calls until the next reload)", + }); return; } + // if a call is active we ignore the request if (callActive) { - console.error("nostr call already executing"); + postMessage(ev, { error: "window.nostr call already executing" }); return; } @@ -86,15 +88,7 @@ async function init() { } } - window.postMessage( - { - application: "LBE", - response: true, - data: response, - scope: "nostr", - }, - "*" // TODO use origin - ); + postMessage(ev, response); }; callActive = true; @@ -106,6 +100,19 @@ async function init() { }); } +function postMessage(ev, response) { + window.postMessage( + { + id: ev.data.id, + application: "LBE", + response: true, + data: response, + scope: "nostr", + }, + "*" + ); +} + init(); export {}; diff --git a/src/extension/inpage-script/index.js b/src/extension/inpage-script/index.js index 0752271ef8..1b6cddf67a 100644 --- a/src/extension/inpage-script/index.js +++ b/src/extension/inpage-script/index.js @@ -1,6 +1,6 @@ import { ABORT_PROMPT_ERROR, USER_REJECTED_ERROR } from "~/common/constants"; -import WebLNProvider from "../ln/webln"; +import WebLNProvider from "../providers/webln"; if (document) { // window.webln is normally loaded onstart (see onstart.js) diff --git a/src/extension/inpage-script/nostr.js b/src/extension/inpage-script/nostr.js index 53b4b5a572..3d8d5b0781 100644 --- a/src/extension/inpage-script/nostr.js +++ b/src/extension/inpage-script/nostr.js @@ -1,4 +1,4 @@ -import NostrProvider from "../ln/nostr"; +import NostrProvider from "../providers/nostr"; if (document) { window.nostr = new NostrProvider(); diff --git a/src/extension/inpage-script/webln.js b/src/extension/inpage-script/webln.js index 64589bb9c2..9278980bdb 100644 --- a/src/extension/inpage-script/webln.js +++ b/src/extension/inpage-script/webln.js @@ -1,5 +1,5 @@ -import WebBTCProvider from "../ln/webbtc"; -import WebLNProvider from "../ln/webln"; +import WebBTCProvider from "../providers/webbtc"; +import WebLNProvider from "../providers/webln"; if (document) { window.webln = new WebLNProvider(); diff --git a/src/extension/ln/nostr/index.ts b/src/extension/ln/nostr/index.ts deleted file mode 100644 index ad30e4131b..0000000000 --- a/src/extension/ln/nostr/index.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { Event } from "./types"; - -export default class NostrProvider { - nip04 = new Nip04(this); - enabled: boolean; - - constructor() { - this.enabled = false; - } - - async enable() { - if (this.enabled) { - return { enabled: true }; - } - return await this.execute<{ - enabled: boolean; - remember: boolean; - }>("enable"); - } - - async getPublicKey(): Promise { - await this.enable(); - return await this.execute("getPublicKeyOrPrompt"); - } - - async signEvent(event: Event): Promise { - await this.enable(); - return this.execute("signEventOrPrompt", { event }); - } - - async signSchnorr(sigHash: string): Promise { - await this.enable(); - return this.execute("signSchnorrOrPrompt", { sigHash }); - } - - async getRelays(): Promise { - await this.enable(); - return this.execute("getRelays"); - } - - // 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: `nostr/${action}`, - scope: "nostr", - 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" || - messageEvent.data.scope !== "nostr" - ) { - 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); - }); - } -} - -class Nip04 { - provider: NostrProvider; - - constructor(provider: NostrProvider) { - this.provider = provider; - } - - async encrypt(peer: string, plaintext: string): Promise { - await this.provider.enable(); - return this.provider.execute("encryptOrPrompt", { peer, plaintext }); - } - - async decrypt(peer: string, ciphertext: string): Promise { - await this.provider.enable(); - return this.provider.execute("decryptOrPrompt", { peer, ciphertext }); - } -} diff --git a/src/extension/providers/nostr/index.ts b/src/extension/providers/nostr/index.ts new file mode 100644 index 0000000000..15f2e67fab --- /dev/null +++ b/src/extension/providers/nostr/index.ts @@ -0,0 +1,70 @@ +import { postMessage } from "../postMessage"; +import { Event } from "./types"; + +declare global { + interface Window { + nostr: NostrProvider; + } +} + +export default class NostrProvider { + nip04 = new Nip04(this); + enabled: boolean; + + constructor() { + this.enabled = false; + } + + async enable() { + if (this.enabled) { + return { enabled: true }; + } + return await this.execute("enable"); + } + + async getPublicKey() { + await this.enable(); + return await this.execute("getPublicKeyOrPrompt"); + } + + async signEvent(event: Event) { + await this.enable(); + return this.execute("signEventOrPrompt", { event }); + } + + async signSchnorr(sigHash: string) { + await this.enable(); + return this.execute("signSchnorrOrPrompt", { sigHash }); + } + + async getRelays() { + await this.enable(); + return this.execute("getRelays"); + } + + // NOTE: new call `action`s must be specified also in the content script + execute( + action: string, + args?: Record + ): Promise> { + return postMessage("nostr", action, args); + } +} + +class Nip04 { + provider: NostrProvider; + + constructor(provider: NostrProvider) { + this.provider = provider; + } + + async encrypt(peer: string, plaintext: string) { + await this.provider.enable(); + return this.provider.execute("encryptOrPrompt", { peer, plaintext }); + } + + async decrypt(peer: string, ciphertext: string) { + await this.provider.enable(); + return this.provider.execute("decryptOrPrompt", { peer, ciphertext }); + } +} diff --git a/src/extension/ln/nostr/types.ts b/src/extension/providers/nostr/types.ts similarity index 100% rename from src/extension/ln/nostr/types.ts rename to src/extension/providers/nostr/types.ts diff --git a/src/extension/providers/postMessage.ts b/src/extension/providers/postMessage.ts new file mode 100644 index 0000000000..8ceed30c92 --- /dev/null +++ b/src/extension/providers/postMessage.ts @@ -0,0 +1,51 @@ +export function postMessage( + scope: string, + action: string, + args: T | undefined +): Promise { + return new Promise((resolve, reject) => { + const id = Math.random().toString().slice(4); + + // 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( + { + id: id, + application: "LBE", + prompt: true, + action: `${scope}/${action}`, + scope: scope, + 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" || + messageEvent.data.scope !== scope || + messageEvent.data.id !== id + ) { + 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); + }); +} diff --git a/src/extension/ln/webbtc/index.ts b/src/extension/providers/webbtc/index.ts similarity index 67% rename from src/extension/ln/webbtc/index.ts rename to src/extension/providers/webbtc/index.ts index 61f0321d24..4b1d125a14 100644 --- a/src/extension/ln/webbtc/index.ts +++ b/src/extension/providers/webbtc/index.ts @@ -1,3 +1,5 @@ +import { postMessage } from "../postMessage"; + type RequestInvoiceArgs = { amount?: string | number; defaultAmount?: string | number; @@ -6,6 +8,12 @@ type RequestInvoiceArgs = { defaultMemo?: string; }; +declare global { + interface Window { + webbtc: WebBTCProvider; + } +} + export default class WebBTCProvider { enabled: boolean; isEnabled: boolean; @@ -102,43 +110,6 @@ export default class WebBTCProvider { 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: "webln", - 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); - }); + return postMessage("webln", action, args); } } diff --git a/src/extension/ln/webln/index.ts b/src/extension/providers/webln/index.ts similarity index 67% rename from src/extension/ln/webln/index.ts rename to src/extension/providers/webln/index.ts index 418afefcb6..74188acc68 100644 --- a/src/extension/ln/webln/index.ts +++ b/src/extension/providers/webln/index.ts @@ -1,3 +1,11 @@ +import { postMessage } from "../postMessage"; + +declare global { + interface Window { + webln: WebLNProvider; + } +} + type RequestInvoiceArgs = { amount?: string | number; defaultAmount?: string | number; @@ -106,44 +114,6 @@ export default class WebLNProvider { 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: "webln", - 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" || - messageEvent.data.scope !== "webln" - ) { - 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); - }); + return postMessage("webln", action, args); } } diff --git a/src/types.ts b/src/types.ts index ac1a254e57..5f6f2ffee3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -7,7 +7,7 @@ import { WebLNNode, } from "~/extension/background-script/connectors/connector.interface"; -import { Event } from "./extension/ln/nostr/types"; +import { Event } from "./extension/providers/nostr/types"; export type ConnectorType = keyof typeof connectors;