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

fix: use actual error objects in NWC Client #209

Merged
merged 7 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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 package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@getalby/sdk",
"version": "3.3.1",
"version": "3.4.0",
"description": "The SDK to integrate with Nostr Wallet Connect and the Alby API",
"repository": "https://github.com/getAlby/js-sdk.git",
"bugs": "https://github.com/getAlby/js-sdk/issues",
Expand Down
164 changes: 113 additions & 51 deletions src/NWCClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,30 @@ export interface NWCOptions {
secret?: string;
}

export class Nip47Error extends Error {
/**
* @deprecated please use message. Deprecated since v3.3.2. Will be removed in v4.0.0.
*/
error: string;
code: string;
constructor(message: string, code: string) {
super(message);
this.error = message;
this.code = code;
}
}

/**
* A NIP-47 response was received, but with an error code (see https://github.com/nostr-protocol/nips/blob/master/47.md#error-codes)
*/
export class Nip47WalletError extends Nip47Error {}
export class Nip47PublishTimeoutError extends Nip47Error {}
export class Nip47ReplyTimeoutError extends Nip47Error {}
export class Nip47PublishError extends Nip47Error {}
export class Nip47ResponseDecodingError extends Nip47Error {}
export class Nip47ResponseValidationError extends Nip47Error {}
export class Nip47UnexpectedResponseError extends Nip47Error {}

export const NWCs: Record<string, NWCOptions> = {
alby: {
authorizationUrl: "https://nwc.getalby.com/apps/new",
Expand Down Expand Up @@ -331,15 +355,14 @@ export class NWCClient {
`height=${height},width=${width},top=${top},left=${left}`,
);
if (!popup) {
reject();
return;
} // only for TS?
throw new Error("failed to execute window.open");
}

const checkForPopup = () => {
if (popup && popup.closed) {
reject();
clearInterval(popupChecker);
window.removeEventListener("message", onMessage);
throw new Error("Popup closed");
}
};

Expand Down Expand Up @@ -558,7 +581,7 @@ export class NWCClient {
resultValidator: (result: T) => boolean,
): Promise<T> {
await this._checkConnected();
return new Promise<T>((resolve, reject) => {
return new Promise<T>((resolve) => {
(async () => {
const command = {
method: nip47Method,
Expand Down Expand Up @@ -590,10 +613,10 @@ export class NWCClient {
function replyTimeout() {
sub.unsub();
//console.error(`Reply timeout: event ${event.id} `);
reject({
error: `reply timeout: event ${event.id}`,
code: "INTERNAL",
});
throw new Nip47ReplyTimeoutError(
`reply timeout: event ${event.id}`,
"INTERNAL",
);
}

const replyTimeoutCheck = setTimeout(replyTimeout, 60000);
Expand All @@ -611,32 +634,52 @@ export class NWCClient {
try {
response = JSON.parse(decryptedContent);
} catch (e) {
reject({ error: "invalid response", code: "INTERNAL" });
return;
clearTimeout(replyTimeoutCheck);
sub.unsub();
throw new Nip47ResponseDecodingError(
"failed to deserialize response",
"INTERNAL",
);
}
if (event.kind == 23195 && response.result) {
// console.info("NIP-47 result", response.result);
if (resultValidator(response.result)) {
resolve(response.result);
if (event.kind == 23195) {
if (response.result) {
// console.info("NIP-47 result", response.result);
if (resultValidator(response.result)) {
resolve(response.result);
} else {
clearTimeout(replyTimeoutCheck);
sub.unsub();
throw new Nip47ResponseValidationError(
"response from NWC failed validation: " +
JSON.stringify(response.result),
"INTERNAL",
);
}
} else {
reject({
error:
"Response from NWC failed validation: " +
JSON.stringify(response.result),
code: "INTERNAL",
});
clearTimeout(replyTimeoutCheck);
sub.unsub();
throw new Nip47WalletError(
response.error?.message || "unknown Error",
response.error?.code || "INTERNAL",
);
}
} else {
reject({
error: response.error?.message,
code: response.error?.code,
});
clearTimeout(replyTimeoutCheck);
sub.unsub();
throw new Nip47UnexpectedResponseError(
response.error?.message || "unknown Error",
response.error?.code || "INTERNAL",
);
}
});

function publishTimeout() {
sub.unsub();
//console.error(`Publish timeout: event ${event.id}`);
reject({ error: `Publish timeout: event ${event.id}` });
throw new Nip47PublishTimeoutError(
`publish timeout: ${event.id}`,
"INTERNAL",
);
}
const publishTimeoutCheck = setTimeout(publishTimeout, 5000);

Expand All @@ -647,7 +690,10 @@ export class NWCClient {
} catch (error) {
//console.error(`Failed to publish to ${this.relay.url}`, error);
clearTimeout(publishTimeoutCheck);
reject({ error: `Failed to publish request: ${error}` });
throw new Nip47PublishError(
`failed to publish: ${error}`,
"INTERNAL",
);
}
})();
});
Expand All @@ -664,7 +710,7 @@ export class NWCClient {
): Promise<(T & { dTag: string })[]> {
await this._checkConnected();
const results: (T & { dTag: string })[] = [];
return new Promise<(T & { dTag: string })[]>((resolve, reject) => {
return new Promise<(T & { dTag: string })[]>((resolve) => {
(async () => {
const command = {
method: nip47Method,
Expand Down Expand Up @@ -696,10 +742,10 @@ export class NWCClient {
function replyTimeout() {
sub.unsub();
//console.error(`Reply timeout: event ${event.id} `);
reject({
error: `reply timeout: event ${event.id}`,
code: "INTERNAL",
});
throw new Nip47ReplyTimeoutError(
`reply timeout: event ${event.id}`,
"INTERNAL",
);
}

const replyTimeoutCheck = setTimeout(replyTimeout, 60000);
Expand All @@ -719,21 +765,31 @@ export class NWCClient {
console.error(e);
clearTimeout(replyTimeoutCheck);
sub.unsub();
reject({ error: "invalid response", code: "INTERNAL" });
return;
throw new Nip47ResponseDecodingError(
"failed to deserialize response",
"INTERNAL",
);
}
if (event.kind == 23195 && response.result) {
// console.info("NIP-47 result", response.result);
try {
if (event.kind == 23195) {
if (response.result) {
// console.info("NIP-47 result", response.result);
if (!resultValidator(response.result)) {
throw new Error(
clearTimeout(replyTimeoutCheck);
sub.unsub();
throw new Nip47ResponseValidationError(
"Response from NWC failed validation: " +
JSON.stringify(response.result),
"INTERNAL",
);
}
const dTag = event.tags.find((tag) => tag[0] === "d")?.[1];
if (dTag === undefined) {
throw new Error("No d tag found in response event");
clearTimeout(replyTimeoutCheck);
sub.unsub();
throw new Nip47ResponseValidationError(
"No d tag found in response event",
"INTERNAL",
);
}
results.push({
...response.result,
Expand All @@ -745,28 +801,31 @@ export class NWCClient {
//console.log("Received results", results);
resolve(results);
}
} catch (error) {
console.error(error);
} else {
clearTimeout(replyTimeoutCheck);
sub.unsub();
reject({
error: (error as Error).message,
code: "INTERNAL",
});
throw new Nip47WalletError(
response.error?.message,
response.error?.code,
);
}
} else {
clearTimeout(replyTimeoutCheck);
sub.unsub();
reject({
error: response.error?.message,
code: response.error?.code,
});
throw new Nip47UnexpectedResponseError(
response.error?.message,
response.error?.code,
);
}
});

function publishTimeout() {
sub.unsub();
//console.error(`Publish timeout: event ${event.id}`);
reject({ error: `Publish timeout: event ${event.id}` });
throw new Nip47PublishTimeoutError(
`Publish timeout: ${event.id}`,
"INTERNAL",
);
}
const publishTimeoutCheck = setTimeout(publishTimeout, 5000);

Expand All @@ -777,7 +836,10 @@ export class NWCClient {
} catch (error) {
//console.error(`Failed to publish to ${this.relay.url}`, error);
clearTimeout(publishTimeoutCheck);
reject({ error: `Failed to publish request: ${error}` });
throw new Nip47PublishError(
`Failed to publish: ${error}`,
"INTERNAL",
);
}
})();
});
Expand Down
Loading