Skip to content
Open
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
8 changes: 5 additions & 3 deletions examples/nwc/client/subscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import "websocket-polyfill"; // required in node.js
import * as readline from "node:readline/promises";
import { stdin as input, stdout as output } from "node:process";

import { NWCClient } from "@getalby/sdk/nwc";
import { NWCClient, Nip47Notification } from "@getalby/sdk/nwc";

const rl = readline.createInterface({ input, output });

Expand All @@ -16,10 +16,12 @@ const client = new NWCClient({
nostrWalletConnectUrl: nwcUrl,
});

const onNotification = (notification) =>
const onNotification = (notification: Nip47Notification) =>
console.info("Got notification", notification);

const unsub = await client.subscribeNotifications(onNotification);
const unsub = await client.subscribeNotifications((n) => {
onNotification(n);
});

console.info("Waiting for notifications...");
process.on("SIGINT", function () {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
},
"dependencies": {
"@getalby/lightning-tools": "^6.0.0",
"nostr-tools": "^2.17.0"
"nostr-tools": "^2.19.4"
},
"devDependencies": {
"@commitlint/cli": "^20.1.0",
Expand Down
103 changes: 35 additions & 68 deletions src/nwc/NWAClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
Nip47NotificationType,
} from "./types";
import { NWCClient } from "./NWCClient";
import { SubCloser } from "nostr-tools/lib/types/abstract-pool";

export type NWAOptions = {
relayUrls: string[];
Expand All @@ -29,7 +28,6 @@ export type NewNWAClientOptions = Omit<NWAOptions, "appPubkey"> & {
appSecretKey?: string;
};

// TODO: add support for multiple relay URLs
export class NWAClient {
options: NWAOptions;
appSecretKey: string;
Expand All @@ -48,7 +46,9 @@ export class NWAClient {
if (!this.options.requestMethods) {
throw new Error("Missing request methods");
}
this.pool = new SimplePool();
this.pool = new SimplePool({
enableReconnect: true,
});

if (globalThis.WebSocket === undefined) {
console.error(
Expand Down Expand Up @@ -175,77 +175,44 @@ export class NWAClient {
}): Promise<{
unsub: () => void;
}> {
let subscribed = true;
let endPromise: (() => void) | undefined;
let sub: SubCloser | undefined;
(async () => {
while (subscribed) {
try {
await this._checkConnected();

sub = this.pool.subscribe(
this.options.relayUrls,
{
kinds: [13194], // NIP-47 info event
"#p": [this.options.appPubkey],
},
{
onevent: async (event) => {
const client = new NWCClient({
relayUrls: this.options.relayUrls,
secret: this.appSecretKey,
walletPubkey: event.pubkey,
});

// try to fetch the lightning address
try {
const info = await client.getInfo();
client.options.lud16 = info.lud16;
client.lud16 = info.lud16;
} catch (error) {
console.error("failed to fetch get_info", error);
}
await this._checkConnected();
console.info("subscribing to info event");

const sub = this.pool.subscribe(
this.options.relayUrls,
{
kinds: [13194], // NIP-47 info event
"#p": [this.options.appPubkey],
},
{
onevent: async (event) => {
const client = new NWCClient({
relayUrls: this.options.relayUrls,
secret: this.appSecretKey,
walletPubkey: event.pubkey,
});

args.onSuccess(client);
// try to fetch the lightning address
try {
const info = await client.getInfo();
client.options.lud16 = info.lud16;
client.lud16 = info.lud16;
} catch (error) {
console.error("failed to fetch get_info", error);
}

subscribed = false;
endPromise?.();
sub?.close();
},
onclose: (reasons) => {
// NOTE: this fires when all relays were closed once. There is no reconnect logic in nostr-tools
// See https://github.com/nbd-wtf/nostr-tools/issues/513
console.info("relay connection closed", reasons);
endPromise?.();
},
},
);
console.info("subscribed to relays");
args.onSuccess(client);

await new Promise<void>((resolve) => {
endPromise = () => {
resolve();
};
});
} catch (error) {
console.error(
"error subscribing to info event",
error || "unknown relay error",
);
}
if (subscribed) {
// wait a second and try re-connecting
// any events during this period will be lost
// unless using a relay that keeps events until client reconnect
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
})();
sub?.close();
},
onclose: (reasons) => {
console.info("subscription closed", reasons);
},
},
);

return {
unsub: () => {
subscribed = false;
endPromise?.();
sub?.close();
},
};
Expand Down
135 changes: 50 additions & 85 deletions src/nwc/NWCClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ import {
Nip47CancelHoldInvoiceResponse,
Nip47NetworkError,
} from "./types";
import { SubCloser } from "nostr-tools/lib/types/abstract-pool";

export interface NWCOptions {
relayUrls: string[];
Expand Down Expand Up @@ -122,8 +121,7 @@ export class NWCClient {
} as NWCOptions;

this.relayUrls = this.options.relayUrls;
this.pool = new SimplePool({
});
this.pool = new SimplePool({ enableReconnect: true });
if (this.options.secret) {
this.secret = (
this.options.secret.toLowerCase().startsWith("nsec")
Expand Down Expand Up @@ -710,91 +708,58 @@ export class NWCClient {
onNotification: (notification: Nip47Notification) => void,
notificationTypes?: Nip47NotificationType[],
): Promise<() => void> {
let subscribed = true;
let endPromise: (() => void) | undefined;
let sub: SubCloser | undefined;
(async () => {
while (subscribed) {
try {
await this._checkConnected();
await this._selectEncryptionType();
console.info("subscribing to relays");
sub = this.pool.subscribe(
this.relayUrls,
{
kinds: [...(this.encryptionType === "nip04" ? [23196] : [23197])],
authors: [this.walletPubkey],
"#p": [this.publicKey],
},
{
onevent: async (event) => {
let decryptedContent;
try {
decryptedContent = await this.decrypt(
this.walletPubkey,
event.content,
);
} catch (error) {
console.error(
"failed to decrypt request event content",
error,
);
return;
}
let notification;
try {
notification = JSON.parse(
decryptedContent,
) as Nip47Notification;
} catch (e) {
console.error("Failed to parse decrypted event content", e);
return;
}
if (notification.notification) {
if (
!notificationTypes ||
notificationTypes.indexOf(notification.notification_type) >
-1
) {
onNotification(notification);
}
} else {
console.error("No notification in response", notification);
}
},
onclose: (reasons) => {
// NOTE: this fires when all relays were closed once. There is no reconnect logic in nostr-tools
// See https://github.com/nbd-wtf/nostr-tools/issues/513
console.info("relay connection closed", reasons);
endPromise?.();
},
},
);
console.info("subscribed to relays");
await this._checkConnected();
await this._selectEncryptionType();

await new Promise<void>((resolve) => {
endPromise = () => {
resolve();
};
});
} catch (error) {
console.error(
"error subscribing to notifications",
error || "unknown relay error",
);
}
if (subscribed) {
// wait a second and try re-connecting
// any notifications during this period will be lost
// unless using a relay that keeps events until client reconnect
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
})();
console.info("subscribing to relays");

const sub = this.pool.subscribe(
this.relayUrls,
{
kinds: [...(this.encryptionType === "nip04" ? [23196] : [23197])],
authors: [this.walletPubkey],
"#p": [this.publicKey],
},
{
onevent: async (event) => {
let decryptedContent;
try {
decryptedContent = await this.decrypt(
this.walletPubkey,
event.content,
);
} catch (error) {
console.error("failed to decrypt request event content", error);
return;
}
let notification;
try {
notification = JSON.parse(decryptedContent) as Nip47Notification;
} catch (e) {
console.error("Failed to parse decrypted event content", e);
return;
}
if (notification.notification) {
if (
!notificationTypes ||
notificationTypes.indexOf(notification.notification_type) > -1
) {
onNotification(notification);
}
} else {
console.error("No notification in response", notification);
}
},
onclose: (reasons) => {
// Since we have auto-reconnect, this usually only fires on fatal errors,
// all relays were closed once or explicit closure, not temp disconnects.
console.warn("subscription closed", reasons);
},
},
);
console.info("subscribed to relays");

return () => {
subscribed = false;
endPromise?.();
sub?.close();
};
}
Expand Down
Loading