From a77123d0b2264312d2c5644edbb83d48e06651a2 Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Tue, 18 Apr 2023 18:14:50 +0200 Subject: [PATCH] Generate secret locally (#4) * Add option to init NostrWeblnProvider with newly, locally created secret this allows apps to generate the secret and pass the public key to NWC * Correct pubkey attribute * cleanup * v1.4.3 * Add initNWC function to initiate the pairing. cont nwc = NostrWebLNProvider.withNewSecret(); nwc.initNWC().then(() => { // nwc is ready }); * Correct Alby NWC URL * Remove message event listener once the success message is processed * Add support for custom NWC URLs * Reject the initNWC promise if the popup is closed * chore: update nwc default relay * doc: add example of generating new NWC connect url * chore: fix ts warning * doc: update nwc withNewSecret example Co-authored-by: Michael Bumann * doc: update README NWC example documentation * chore: make options parameter optional --------- Co-authored-by: Roland Bewick Co-authored-by: Roland <33993199+rolznz@users.noreply.github.com> --- README.md | 18 ++++++- package.json | 2 +- src/webln/NostrWeblnProvider.ts | 86 ++++++++++++++++++++++++++++++--- 3 files changed, 96 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 150543f..27caf2c 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,6 @@ The `NostrWebLNProvider` exposes the [WebLN](webln.guide/) sendPayment interface (note: in the future more WebLN functions will be added to Nostr Wallet Connect) - ### NostrWebLNProvider Options * `nostrWalletConnectUrl`: the full Nostr Wallet Connect URL as defined by the [spec](https://github.com/getAlby/nips/blob/master/47.md) @@ -53,7 +52,7 @@ global.crypto = crypto; ```js import { NostrWebLNProvider } from 'alby-js-sdk'; -const webln = new NostrWebLNProvider(); // use defaults (will use window.nostr to sign the request) +const webln = new NostrWebLNProvider(); // use defaults (connects to Alby's relay, will use window.nostr to sign the request) await webln.enable(); // connect to the relay const response = await webln.sendPayment(invoice); console.log(response.preimage); @@ -73,6 +72,21 @@ console.log(response.preimage); webln.close(); // close the websocket connection ``` +#### Generate a new NWC connect url using a locally-generated secret +```js +// same options can be provided to .withNewSecret() as creating a new NostrWebLNProvider() +const webln = webln.NostrWebLNProvider.withNewSecret(); +await webln.initNWC("alby", { + name: `My app name`, +}); + +// ... enable and send a payment + +// if you want to get the connect url with the secret: +// const nostrWalletConnectUrl nwc.getNostrWalletConnectUrl(true) + +``` + ## OAuth API Documentation Please have a look a the Alby OAuth2 Wallet API: diff --git a/package.json b/package.json index 68a94c1..47bb9f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "alby-js-sdk", - "version": "1.4.2", + "version": "1.4.3", "description": "Alby OAuth2 Client", "type": "module", "source": "src/index.ts", diff --git a/src/webln/NostrWeblnProvider.ts b/src/webln/NostrWeblnProvider.ts index 027bb64..48c918c 100644 --- a/src/webln/NostrWeblnProvider.ts +++ b/src/webln/NostrWeblnProvider.ts @@ -3,34 +3,40 @@ import { relayInit, signEvent, getEventHash, - getPublicKey, nip19, + generatePrivateKey, + getPublicKey, Relay, Event, UnsignedEvent } from 'nostr-tools'; const DEFAULT_OPTIONS = { - relayUrl: 'wss://relay.damus.io', + relayUrl: "wss://relay.getalby.com/v1", walletPubkey: '69effe7b49a6dd5cf525bd0905917a5005ffe480b58eeb8e861418cf3ae760d9' // Alby }; +const NWC_URLS = { + alby: "https://nwc.getalby.com/apps/new" +}; + interface Nostr { signEvent: (event: UnsignedEvent) => Promise; nip04: { decrypt: (pubkey: string, content: string) => Promise; encrypt: (pubkey: string, content: string) => Promise; }; -} +}; + declare global { var nostr: Nostr | undefined; -} +}; interface NostrWebLNOptions { relayUrl: string; walletPubkey: string; secret?: string; -} +}; export class NostrWebLNProvider { @@ -55,13 +61,20 @@ export class NostrWebLNProvider { } return options; } - constructor(options: { relayUrl?: string, secret?: string, walletPubkey?: string, nostrWalletConnectUrl?: string }) { + + static withNewSecret(options?: ConstructorParameters[0]) { + options = options || {}; + options.secret = generatePrivateKey(); + return new NostrWebLNProvider(options); + } + + constructor(options?: { relayUrl?: string, secret?: string, walletPubkey?: string, nostrWalletConnectUrl?: string }) { if (options && options.nostrWalletConnectUrl) { options = { ...NostrWebLNProvider.parseWalletConnectUrl(options.nostrWalletConnectUrl), ...options }; } - const _options = { ...DEFAULT_OPTIONS, ...options } as NostrWebLNOptions; + const _options = { ...DEFAULT_OPTIONS, ...(options || {}) } as NostrWebLNOptions; this.relayUrl = _options.relayUrl; this.relay = relayInit(this.relayUrl); if (_options.secret) { @@ -82,6 +95,18 @@ export class NostrWebLNProvider { } } + getNostrWalletConnectUrl(includeSecret = false) { + let url = `nostrwalletconnect://${this.walletPubkey}?relay=${this.relayUrl}&pubkey=${this.publicKey}`; + if (includeSecret) { + url = `${url}&secret=${this.secret}`; + } + return url; + } + + get nostrWalletConnectUrl() { + return this.getNostrWalletConnectUrl(); + } + get connected() { return this.relay.status === 1; } @@ -213,6 +238,53 @@ export class NostrWebLNProvider { }); }); } + + initNWC(providerNameOrUrl: string, options: { name: string, returnTo?: string }) { + const height = 600; + const width = 400; + const top = window.outerHeight / 2 + window.screenY - height / 2; + const left = window.outerWidth / 2 + window.screenX - width / 2; + + const urlStr = NWC_URLS[providerNameOrUrl as keyof typeof NWC_URLS] || providerNameOrUrl; + const url = new URL(urlStr); + url.searchParams.set('c', options.name); + url.searchParams.set('pubkey', this.publicKey); + url.searchParams.set('url', document.location.origin); + if (options.returnTo) { + url.searchParams.set('returnTo', options.returnTo); + } + return new Promise((resolve, reject) => { + const popup = window.open( + url.toString(), + `${document.title} - Wallet Connect`, + `height=${height},width=${width},top=${top},left=${left}` + ); + if (!popup) { reject(); return; } // only for TS? + + const checkForPopup = () => { + if (popup && popup.closed) { + reject(); + clearInterval(popupChecker); + window.removeEventListener('message', onMessage); + } + }; + + const onMessage = (message: { data: any, origin: string }) => { + const data = message.data; + if (data && data.type === 'nwc:success' && message.origin === `${url.protocol}//${url.host}`) { + resolve(data); + clearInterval(popupChecker); + window.removeEventListener('message', onMessage); + if (popup) { + popup.close(); // close the popup + } + } + }; + const popupChecker = setInterval(checkForPopup, 500); + window.addEventListener('message', onMessage); + }); + } + private checkConnected() { if (!this.connected) { throw new Error("please call enable() and await the promise before calling this function")