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

Add opt-in logging with verbose flag #253

Open
wants to merge 1 commit into
base: development
Choose a base branch
from
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
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,24 @@ Go to the [docs](https://cashubtc.github.io/cashu-ts/docs/main) for detailed usa
npm i @cashu/cashu-ts
```

### Logging

By default, cashu-ts does not log to the console. If you want to enable logging for debugging purposes, you can set the `verbose` option when creating a wallet:

```typescript
import { CashuMint, CashuWallet } from '@cashu/cashu-ts';
const mintUrl = 'http://localhost:3338';
const mint = new CashuMint(mintUrl, undefined, { verbose: true }); // Enable logging for the mint
const wallet = new CashuWallet(mint, { verbose: true }); // Enable logging for the wallet
```

This will log various operations such as:
- WebSocket connections and subscriptions
- Mint and melt quote creation
- Proof mapping and validation
- Non-deterministic secret generation warnings
- DLEQ validation failures

### Examples

#### Mint tokens
Expand Down
42 changes: 28 additions & 14 deletions src/CashuMint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import type {
MeltQuoteResponse
} from './model/types/index.js';
import { MeltQuoteState } from './model/types/index.js';
import request from './request.js';
import request, { setRequestVerbose } from './request.js';
import { isObj, joinUrls, sanitizeUrl } from './utils.js';
import {
MeltQuoteResponsePaidDeprecated,
Expand All @@ -35,13 +35,32 @@ import { handleMintInfoContactFieldDeprecated } from './legacy/nut-06.js';
*/
class CashuMint {
private ws?: WSConnection;
private _verbose = false;

/**
* Internal method for logging messages when verbose mode is enabled
* @param message Message to log
* @param optionalParams Additional parameters to log
*/
private log(message: string, ...optionalParams: Array<any>): void {
if (this._verbose) {
console.log(message, ...optionalParams);
}
}

/**
* @param _mintUrl requires mint URL to create this object
* @param _customRequest if passed, use custom request implementation for network communication with the mint
* @param options.verbose Whether to enable verbose logging
*/
constructor(private _mintUrl: string, private _customRequest?: typeof request) {
constructor(private _mintUrl: string, private _customRequest?: typeof request, options?: { verbose?: boolean }) {
this._mintUrl = sanitizeUrl(_mintUrl);
this._customRequest = _customRequest;
if (options?.verbose) {
this._verbose = options.verbose;
// Set verbose mode for request module
setRequestVerbose(options.verbose);
}
}

get mintUrl() {
Expand Down Expand Up @@ -445,25 +464,20 @@ class CashuMint {
* Tries to establish a websocket connection with the websocket mint url according to NUT-17
*/
async connectWebSocket() {
if (this.ws) {
await this.ws.ensureConnection();
} else {
if (!this.ws) {
const mintUrl = new URL(this._mintUrl);
const wsSegment = 'v1/ws';
if (mintUrl.pathname) {
if (mintUrl.pathname.endsWith('/')) {
mintUrl.pathname += wsSegment;
} else {
mintUrl.pathname += '/' + wsSegment;
}
// Set verbose mode on the ConnectionManager
const connectionManager = ConnectionManager.getInstance();
if (this._verbose) {
connectionManager.setVerbose(this._verbose);
}
this.ws = ConnectionManager.getInstance().getConnection(
this.ws = connectionManager.getConnection(
`${mintUrl.protocol === 'https:' ? 'wss' : 'ws'}://${mintUrl.host}${mintUrl.pathname}`
);
try {
await this.ws.connect();
} catch (e) {
console.log(e);
this.log('Failed to connect to WebSocket:', e);
throw new Error('Failed to connect to WebSocket...');
}
}
Expand Down
34 changes: 33 additions & 1 deletion src/CashuWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,32 @@ class CashuWallet {
private _mintInfo: MintInfo | undefined = undefined;
private _denominationTarget = DEFAULT_DENOMINATION_TARGET;
private _keepFactory: OutputDataFactory | undefined;
private _verbose = false;

mint: CashuMint;

/**
* Internal method for logging messages when verbose mode is enabled
* @param message Message to log
* @param optionalParams Additional parameters to log
*/
private log(message: string, ...optionalParams: Array<any>): void {
if (this._verbose) {
console.log(message, ...optionalParams);
}
}

/**
* Internal method for logging warnings when verbose mode is enabled
* @param message Warning message to log
* @param optionalParams Additional parameters to log
*/
private warn(message: string, ...optionalParams: Array<any>): void {
if (this._verbose) {
console.warn(message, ...optionalParams);
}
}

/**
* @param mint Cashu mint instance is used to make api calls
* @param options.unit optionally set unit (default is 'sat')
Expand All @@ -111,6 +134,7 @@ class CashuWallet {
bip39seed?: Uint8Array;
denominationTarget?: number;
keepFactory?: OutputDataFactory;
verbose?: boolean;
}
) {
this.mint = mint;
Expand Down Expand Up @@ -138,6 +162,9 @@ class CashuWallet {
if (options?.keepFactory) {
this._keepFactory = options.keepFactory;
}
if (options?.verbose) {
this._verbose = options.verbose;
}
}

get unit(): string {
Expand Down Expand Up @@ -547,7 +574,7 @@ class CashuWallet {
}

if (amountToSend + this.getFeesForProofs(proofsToSend) > amountAvailable) {
console.error(
this.warn(
`Not enough funds available (${amountAvailable}) for swap amountToSend: ${amountToSend} + fee: ${this.getFeesForProofs(
proofsToSend
)} | length: ${proofsToSend.length}`
Expand Down Expand Up @@ -666,6 +693,7 @@ class CashuWallet {
amount: amount,
description: description
};
this.log(`Creating mint quote for amount: ${amount} with description: ${description}`);
return await this.mint.createMintQuote(mintQuotePayload);
}

Expand Down Expand Up @@ -1067,6 +1095,7 @@ class CashuWallet {
);
return () => {
this.mint.webSocketConnection?.cancelSubscription(subId, callback);
this.log(`Cancelled subscription ${subId} for ${quoteIds.length} mint quotes`);
};
}

Expand Down Expand Up @@ -1139,6 +1168,7 @@ class CashuWallet {
);
return () => {
this.mint.webSocketConnection?.cancelSubscription(subId, callback);
this.log(`Cancelled subscription ${subId} for ${quoteIds.length} melt quotes`);
};
}

Expand All @@ -1162,6 +1192,7 @@ class CashuWallet {
const proofMap: { [y: string]: Proof } = {};
for (let i = 0; i < proofs.length; i++) {
const y = hashToCurve(enc.encode(proofs[i].secret)).toHex(true);
this.log(`Mapping proof ${i+1}/${proofs.length} to Y point: ${y}`);
proofMap[y] = proofs[i];
}
const ys = Object.keys(proofMap);
Expand All @@ -1174,6 +1205,7 @@ class CashuWallet {
);
return () => {
this.mint.webSocketConnection?.cancelSubscription(subId, callback);
this.log(`Cancelled subscription ${subId} for ${ys.length} proofs`);
};
}

Expand Down
50 changes: 41 additions & 9 deletions src/WSConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import {
RpcSubId
} from './model/types';
import { OnOpenError, OnOpenSuccess } from './model/types/wallet/websocket';
import { getWebSocketImpl } from './ws';

export class ConnectionManager {
static instace: ConnectionManager;
private connectionMap: Map<string, WSConnection> = new Map();
private _verbose = false;

static getInstance() {
if (!ConnectionManager.instace) {
Expand All @@ -20,13 +20,19 @@ export class ConnectionManager {
return ConnectionManager.instace;
}

/**
* Set verbose mode for WebSocket connections
* @param isVerbose
*/
setVerbose(isVerbose: boolean): void {
this._verbose = isVerbose;
}

getConnection(url: string): WSConnection {
if (this.connectionMap.has(url)) {
return this.connectionMap.get(url) as WSConnection;
if (!this.connectionMap.has(url)) {
this.connectionMap.set(url, new WSConnection(url, { verbose: this._verbose }));
}
const newConn = new WSConnection(url);
this.connectionMap.set(url, newConn);
return newConn;
return this.connectionMap.get(url) as WSConnection;
}
}

Expand All @@ -40,11 +46,37 @@ export class WSConnection {
private messageQueue: MessageQueue;
private handlingInterval?: number;
private rpcId = 0;
private _verbose = false;

constructor(url: string) {
this._WS = getWebSocketImpl();
constructor(url: string, options?: { verbose?: boolean }) {
this.url = new URL(url);
this._WS = typeof WebSocket !== 'undefined' ? WebSocket : require('ws');
this.messageQueue = new MessageQueue();
if (options?.verbose) {
this._verbose = options.verbose;
}
}

/**
* Internal method for logging messages when verbose mode is enabled
* @param message Message to log
* @param optionalParams Additional parameters to log
*/
private log(message: string, ...optionalParams: Array<any>): void {
if (this._verbose) {
console.log(message, ...optionalParams);
}
}

/**
* Internal method for logging errors when verbose mode is enabled
* @param message Error message to log
* @param optionalParams Additional parameters to log
*/
private error(message: string, ...optionalParams: Array<any>): void {
if (this._verbose) {
console.error(message, ...optionalParams);
}
}

connect() {
Expand Down Expand Up @@ -162,7 +194,7 @@ export class WSConnection {
}
}
} catch (e) {
console.error(e);
this.error('Error processing WebSocket message:', e);
return;
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/legacy/nut-04.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { MintQuoteResponse } from '../model/types/index.js';
import { MintQuoteState } from '../model/types/index.js';
import { logWarning } from '../utils.js';

export type MintQuoteResponsePaidDeprecated = {
paid?: boolean;
Expand All @@ -10,7 +11,7 @@ export function handleMintQuoteResponseDeprecated(
): MintQuoteResponse {
// if the response MeltQuoteResponse has a "paid" flag, we monkey patch it to the state enum
if (!response.state) {
console.warn(
logWarning(
"Field 'state' not found in MintQuoteResponse. Update NUT-04 of mint: https://github.com/cashubtc/nuts/pull/141)"
);
if (typeof response.paid === 'boolean') {
Expand Down
3 changes: 2 additions & 1 deletion src/legacy/nut-05.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { MeltQuoteResponse } from '../model/types/index.js';
import { MeltQuoteState } from '../model/types/index.js';
import { logWarning } from '../utils.js';

export type MeltQuoteResponsePaidDeprecated = {
paid?: boolean;
Expand All @@ -10,7 +11,7 @@ export function handleMeltQuoteResponseDeprecated(
): MeltQuoteResponse {
// if the response MeltQuoteResponse has a "paid" flag, we monkey patch it to the state enum
if (!response.state) {
console.warn(
logWarning(
"Field 'state' not found in MeltQuoteResponse. Update NUT-05 of mint: https://github.com/cashubtc/nuts/pull/136)"
);
if (typeof response.paid === 'boolean') {
Expand Down
3 changes: 2 additions & 1 deletion src/legacy/nut-06.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { MintContactInfo, GetInfoResponse } from '../model/types/index.js';
import { logWarning } from '../utils.js';

export function handleMintInfoContactFieldDeprecated(data: GetInfoResponse) {
// Monkey patch old contact field ["email", "me@mail.com"] Array<[string, string]>; to new contact field [{method: "email", info: "me@mail.com"}] Array<MintContactInfo>
Expand All @@ -11,7 +12,7 @@ export function handleMintInfoContactFieldDeprecated(data: GetInfoResponse) {
typeof contact[0] === 'string' &&
typeof contact[1] === 'string'
) {
console.warn(
logWarning(
`Mint returned deprecated 'contact' field: Update NUT-06: https://github.com/cashubtc/nuts/pull/117`
);
return { method: contact[0], info: contact[1] } as MintContactInfo;
Expand Down
Loading