rpc-express-toolkit 2.0.3 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
4
4
  [![npm version](https://img.shields.io/npm/v/rpc-express-toolkit.svg)](https://www.npmjs.com/package/rpc-express-toolkit)
5
- [![Build Status](https://github.com/n-car/rpc-express-toolkit/actions/workflows/main.yml/badge.svg)](https://github.com/n-car/rpc-express-toolkit/actions)
5
+ [![Build Status](https://github.com/n-car/rpc-express-toolkit/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/n-car/rpc-express-toolkit/actions/workflows/ci.yml)
6
6
  [![Coverage Status](https://coveralls.io/repos/github/n-car/rpc-express-toolkit/badge.svg?branch=main)](https://coveralls.io/github/n-car/rpc-express-toolkit?branch=main)
7
7
 
8
8
  An enterprise-ready JSON-RPC 2.0 toolkit for Express.js applications with simplified APIs, structured logging, middleware system, schema validation, batch processing, and comprehensive BigInt/Date serialization support.
@@ -514,6 +514,43 @@ logger.error('Database error', {
514
514
  // [2024-01-01T10:00:00.050Z] INFO: RPC success - {"method":"getUser","id":"req-123","duration":50,"resultSize":245}
515
515
  ```
516
516
 
517
+ ### Client Configuration
518
+
519
+ The `RpcClient` supports various configuration options for different environments, **inclusa la gestione di certificati self-signed**:
520
+
521
+ ```javascript
522
+ const fs = require('fs');
523
+ const { RpcClient } = require('rpc-express-toolkit');
524
+
525
+ // Basic client
526
+ const client = new RpcClient('https://api.example.com/rpc');
527
+
528
+ // Client con header personalizzati
529
+ const clientWithHeaders = new RpcClient('https://api.example.com/rpc', {
530
+ 'Authorization': 'Bearer your-token',
531
+ 'X-API-Key': 'your-api-key',
532
+ 'User-Agent': 'MyApp/1.0'
533
+ });
534
+
535
+ // Client per sviluppo con certificato self-signed (accetta qualsiasi certificato, NON usare in produzione)
536
+ const devClient = new RpcClient('https://localhost:3000/api', {
537
+ 'Authorization': 'Bearer dev-token'
538
+ }, {
539
+ rejectUnauthorized: false // Solo per sviluppo!
540
+ });
541
+
542
+ // Client per sviluppo con CA self-signed (modo sicuro: accetta solo la tua CA)
543
+ const ca = fs.readFileSync('path/to/your/selfsigned-ca.pem');
544
+ const devClientWithCA = new RpcClient('https://localhost:3000/api', {
545
+ 'Authorization': 'Bearer dev-token'
546
+ }, {
547
+ ca // Passa il certificato della tua CA self-signed
548
+ // oppure: agent: new require('https').Agent({ ca, rejectUnauthorized: true })
549
+ });
550
+ ```
551
+
552
+ **⚠️ Security Warning**: Usa `rejectUnauthorized: false` solo in sviluppo. Per maggiore sicurezza, preferisci sempre la soluzione con `ca` per accettare solo la tua CA self-signed. In produzione usa sempre certificati validi e firmati da una CA attendibile.
553
+
517
554
  ### Built-in Middlewares
518
555
 
519
556
  ```javascript
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Example demonstrating how to use RpcClient with self-signed certificates
3
+ * in development environments.
4
+ *
5
+ * WARNING: Only use rejectUnauthorized: false in development!
6
+ * Never use this in production as it disables SSL certificate validation.
7
+ */
8
+
9
+ const { RpcClient } = require('../src/index.js');
10
+
11
+ async function demonstrateSSLOptions() {
12
+ console.log('=== RPC Client SSL Configuration Example ===\n');
13
+
14
+ // Example 1: Standard production client (default - strict SSL)
15
+ console.log('1. Production Client (strict SSL validation):');
16
+ const productionClient = new RpcClient('https://api.example.com/rpc', {
17
+ 'Authorization': 'Bearer prod-token'
18
+ });
19
+ console.log(' ✓ Created client with strict SSL validation (default)\n');
20
+
21
+
22
+ // Example 2: Development client for self-signed certificates (bypasses ALL SSL validation)
23
+ console.log('2. Development Client (bypasses SSL validation):');
24
+ const developmentClient = new RpcClient('https://localhost:3000/api', {
25
+ 'Authorization': 'Bearer dev-token',
26
+ 'X-Environment': 'development'
27
+ }, {
28
+ rejectUnauthorized: false // Solo per sviluppo!
29
+ });
30
+ console.log(' ✓ Created client that bypasses SSL certificate validation');
31
+ console.log(' ⚠️ WARNING: Only use this for development with self-signed certificates!\n');
32
+
33
+ // Example 3: Development client with custom CA (accepts only your self-signed CA)
34
+ console.log('3. Development Client with custom CA:');
35
+ const fs = require('fs');
36
+ const ca = fs.readFileSync('path/to/your/selfsigned-ca.pem');
37
+ const devClientWithCA = new RpcClient('https://localhost:3000/api', {
38
+ 'Authorization': 'Bearer dev-token',
39
+ 'X-Environment': 'development'
40
+ }, {
41
+ ca // Passa il certificato della tua CA self-signed
42
+ });
43
+ console.log(' ✓ Created client that accepts only your self-signed CA');
44
+ console.log(' ⚠️ WARNING: This is safer than disabling validation, but use valid certificates in production!\n');
45
+
46
+ // Example 4: Demonstrate how to choose configuration based on environment
47
+ console.log('4. Environment-based Configuration:');
48
+ const isDevelopment = process.env.NODE_ENV === 'development';
49
+ const baseUrl = isDevelopment ? 'https://localhost:3000/api' : 'https://api.production.com/rpc';
50
+ const environmentClient = new RpcClient(baseUrl, {
51
+ 'Authorization': `Bearer ${process.env.API_TOKEN || 'default-token'}`,
52
+ 'X-Environment': process.env.NODE_ENV || 'development'
53
+ }, isDevelopment ? { ca } : {});
54
+ console.log(` ✓ Created client for ${isDevelopment ? 'development' : 'production'} environment`);
55
+ console.log(` ✓ SSL validation: ${isDevelopment ? 'custom CA (dev only)' : 'enabled (secure)'}\n`);
56
+
57
+ // Example usage (commented out since we don't have a real server)
58
+ /*
59
+ try {
60
+ // This would work with a real server
61
+ const result = await developmentClient.call('ping', {});
62
+ console.log('Server response:', result);
63
+ } catch (error) {
64
+ console.error('Error calling server:', error.message);
65
+ }
66
+ */
67
+
68
+ console.log('4. Best Practices:');
69
+ console.log(' ✓ Always use strict SSL validation in production');
70
+ console.log(' ✓ Only disable SSL validation for development with self-signed certificates');
71
+ console.log(' ✓ Use environment variables to control SSL behavior');
72
+ console.log(' ✓ Never commit rejectUnauthorized: false to production code');
73
+ console.log(' ✓ Consider using proper development certificates instead of disabling validation\n');
74
+
75
+ console.log('=== Example Complete ===');
76
+ }
77
+
78
+ // Run the example if this file is executed directly
79
+ if (require.main === module) {
80
+ demonstrateSSLOptions().catch(console.error);
81
+ }
82
+
83
+ module.exports = { demonstrateSSLOptions };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rpc-express-toolkit",
3
- "version": "2.0.3",
3
+ "version": "2.2.0",
4
4
  "description": "Enterprise-ready JSON-RPC 2.0 toolkit for Express.js with simplified APIs, structured logging, middleware system, schema validation, and comprehensive BigInt/Date support.",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -12,6 +12,8 @@
12
12
  ],
13
13
  "scripts": {
14
14
  "test": "jest",
15
+ "test:coverage": "jest --coverage",
16
+ "test:watch": "jest --watch",
15
17
  "lint": "eslint src/ examples/",
16
18
  "lint:fix": "eslint src/ examples/ --fix",
17
19
  "clean": "node -e \"require('fs').existsSync('dist') && require('fs').rmSync('dist', {recursive: true})\"",
@@ -33,12 +33,15 @@ class RpcClient {
33
33
  #endpoint;
34
34
  #defaultHeaders;
35
35
  #requestId;
36
+ #fetchOptions;
36
37
 
37
38
  /**
38
39
  * @param {string} endpoint The JSON-RPC endpoint URL.
39
40
  * @param {Object} [defaultHeaders={}] Optional default headers to include in requests.
41
+ * @param {Object} [options={}] Optional configuration options.
42
+ * @param {boolean} [options.rejectUnauthorized=true] Whether to reject unauthorized SSL certificates. Set to false for development with self-signed certificates.
40
43
  */
41
- constructor(endpoint, defaultHeaders = {}) {
44
+ constructor(endpoint, defaultHeaders = {}, options = {}) {
42
45
  this.#endpoint = endpoint;
43
46
  this.#defaultHeaders = {
44
47
  "Content-Type": "application/json", // Default header
@@ -46,6 +49,32 @@ class RpcClient {
46
49
  };
47
50
  // Initialize request ID counter with timestamp + random component for uniqueness
48
51
  this.#requestId = Date.now() * 1000 + Math.floor(Math.random() * 1000);
52
+
53
+ // Store fetch options for Node.js environments
54
+ this.#fetchOptions = {};
55
+ // Support for custom https.Agent (e.g. for self-signed CA)
56
+ if (typeof process !== 'undefined' && process.env && typeof require !== 'undefined') {
57
+ try {
58
+ const https = require('https');
59
+ // If options.agent is provided, use it directly
60
+ if (options.agent) {
61
+ this.#fetchOptions.agent = options.agent;
62
+ } else if (options.ca) {
63
+ // If options.ca is provided, create an agent with the custom CA
64
+ this.#fetchOptions.agent = new https.Agent({
65
+ ca: options.ca,
66
+ rejectUnauthorized: options.rejectUnauthorized !== false // true by default
67
+ });
68
+ } else if (options.rejectUnauthorized === false) {
69
+ // Legacy: allow disabling SSL validation for dev
70
+ this.#fetchOptions.agent = new https.Agent({
71
+ rejectUnauthorized: false
72
+ });
73
+ }
74
+ } catch (e) {
75
+ // Ignore if https module is not available
76
+ }
77
+ }
49
78
  }
50
79
 
51
80
  /**
@@ -85,6 +114,7 @@ class RpcClient {
85
114
  // Since we have BigInt.prototype.toJSON, we don't need manual serialization
86
115
  // JSON.stringify will handle BigInt and Date automatically
87
116
  body: JSON.stringify(requestBody),
117
+ ...this.#fetchOptions, // Include any additional fetch options (e.g., SSL settings)
88
118
  });
89
119
 
90
120
  if (!response.ok) {
@@ -136,6 +166,7 @@ class RpcClient {
136
166
  ...overrideHeaders,
137
167
  },
138
168
  body: JSON.stringify(batchRequests),
169
+ ...this.#fetchOptions, // Include any additional fetch options (e.g., SSL settings)
139
170
  });
140
171
 
141
172
  if (!response.ok) {
@@ -1 +1 @@
1
- let fetchFn;if("undefined"==typeof BigInt||BigInt.prototype.toJSON||(BigInt.prototype.toJSON=function(){return this.toString()}),"undefined"!=typeof fetch)fetchFn=fetch;else try{fetchFn=require("node-fetch")}catch(t){if("undefined"==typeof globalThis||!globalThis.fetch)throw new Error("fetch is not available. Please install node-fetch or use Node.js 18+");fetchFn=globalThis.fetch}class RpcClient{#t;#e;#r;constructor(t,e={}){this.#t=t,this.#e={"Content-Type":"application/json",...e},this.#r=1e3*Date.now()+Math.floor(1e3*Math.random())}#i(){return++this.#r}async call(t,e={},r=void 0,i={}){const s={jsonrpc:"2.0",method:t,params:e,id:void 0===r?this.#i():r};try{const t=await fetchFn(this.#t,{method:"POST",headers:{...this.#e,...i},body:JSON.stringify(s)});if(!t.ok)throw new Error(`HTTP Error: ${t.status} ${t.statusText}`);const e=await t.json();if(e.error)throw e.error;return this.deserializeBigIntsAndDates(e.result)}catch(t){throw console.error("RPC call failed:",t),t}}async notify(t,e={},r={}){await this.call(t,e,null,r)}async batch(t,e={}){const r=t.map(t=>({jsonrpc:"2.0",method:t.method,params:t.params||{},id:void 0!==t.id?t.id:this.#i()}));try{const t=await fetchFn(this.#t,{method:"POST",headers:{...this.#e,...e},body:JSON.stringify(r)});if(!t.ok)throw new Error(`HTTP Error: ${t.status} ${t.statusText}`);const i=await t.json();if(Array.isArray(i))return i.map(t=>{if(t.error)throw t.error;return this.deserializeBigIntsAndDates(t.result)});if(i.error)throw i.error;return[this.deserializeBigIntsAndDates(i.result)]}catch(t){throw console.error("Batch RPC call failed:",t),t}}serializeBigIntsAndDates(t){if("bigint"==typeof t)return t.toString();if(Array.isArray(t))return t.map(t=>this.serializeBigIntsAndDates(t));if(t instanceof Date)return t.toISOString();if(t&&"object"==typeof t){const e={};for(const[r,i]of Object.entries(t))e[r]=this.serializeBigIntsAndDates(i);return e}return t}deserializeBigIntsAndDates(t){const e=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/;if("string"==typeof t){if(/^-?\d+n?$/.test(t))return BigInt(t.replace(/n$/,""));if(e.test(t)){const e=new Date(t);if(!isNaN(e.getTime()))return e}}return Array.isArray(t)?t.map(t=>this.deserializeBigIntsAndDates(t)):t&&"object"==typeof t?Object.fromEntries(Object.entries(t).map(([t,e])=>[t,this.deserializeBigIntsAndDates(e)])):t}}module.exports=RpcClient,module.exports.RpcClient=RpcClient;
1
+ let fetchFn;if("undefined"==typeof BigInt||BigInt.prototype.toJSON||(BigInt.prototype.toJSON=function(){return this.toString()}),"undefined"!=typeof fetch)fetchFn=fetch;else try{fetchFn=require("node-fetch")}catch(t){if("undefined"==typeof globalThis||!globalThis.fetch)throw new Error("fetch is not available. Please install node-fetch or use Node.js 18+");fetchFn=globalThis.fetch}class RpcClient{#t;#e;#r;#i;constructor(t,e={},r={}){if(this.#t=t,this.#e={"Content-Type":"application/json",...e},this.#r=1e3*Date.now()+Math.floor(1e3*Math.random()),this.#i={},"undefined"!=typeof process&&process.env&&"undefined"!=typeof require)try{const t=require("https");r.agent?this.#i.agent=r.agent:r.ca?this.#i.agent=new t.Agent({ca:r.ca,rejectUnauthorized:!1!==r.rejectUnauthorized}):!1===r.rejectUnauthorized&&(this.#i.agent=new t.Agent({rejectUnauthorized:!1}))}catch(t){}}#n(){return++this.#r}async call(t,e={},r=void 0,i={}){const n={jsonrpc:"2.0",method:t,params:e,id:void 0===r?this.#n():r};try{const t=await fetchFn(this.#t,{method:"POST",headers:{...this.#e,...i},body:JSON.stringify(n),...this.#i});if(!t.ok)throw new Error(`HTTP Error: ${t.status} ${t.statusText}`);const e=await t.json();if(e.error)throw e.error;return this.deserializeBigIntsAndDates(e.result)}catch(t){throw console.error("RPC call failed:",t),t}}async notify(t,e={},r={}){await this.call(t,e,null,r)}async batch(t,e={}){const r=t.map(t=>({jsonrpc:"2.0",method:t.method,params:t.params||{},id:void 0!==t.id?t.id:this.#n()}));try{const t=await fetchFn(this.#t,{method:"POST",headers:{...this.#e,...e},body:JSON.stringify(r),...this.#i});if(!t.ok)throw new Error(`HTTP Error: ${t.status} ${t.statusText}`);const i=await t.json();if(Array.isArray(i))return i.map(t=>{if(t.error)throw t.error;return this.deserializeBigIntsAndDates(t.result)});if(i.error)throw i.error;return[this.deserializeBigIntsAndDates(i.result)]}catch(t){throw console.error("Batch RPC call failed:",t),t}}serializeBigIntsAndDates(t){if("bigint"==typeof t)return t.toString();if(Array.isArray(t))return t.map(t=>this.serializeBigIntsAndDates(t));if(t instanceof Date)return t.toISOString();if(t&&"object"==typeof t){const e={};for(const[r,i]of Object.entries(t))e[r]=this.serializeBigIntsAndDates(i);return e}return t}deserializeBigIntsAndDates(t){const e=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/;if("string"==typeof t){if(/^-?\d+n?$/.test(t))return BigInt(t.replace(/n$/,""));if(e.test(t)){const e=new Date(t);if(!isNaN(e.getTime()))return e}}return Array.isArray(t)?t.map(t=>this.deserializeBigIntsAndDates(t)):t&&"object"==typeof t?Object.fromEntries(Object.entries(t).map(([t,e])=>[t,this.deserializeBigIntsAndDates(e)])):t}}module.exports=RpcClient,module.exports.RpcClient=RpcClient;
@@ -1 +1 @@
1
- let t;if("undefined"==typeof BigInt||BigInt.prototype.toJSON||(BigInt.prototype.toJSON=function(){return this.toString()}),"undefined"!=typeof fetch)t=fetch;else try{const{default:e}=await import("node-fetch");t=e}catch(e){if("undefined"==typeof globalThis||!globalThis.fetch)throw new Error("fetch is not available. Please install node-fetch or use Node.js 18+");t=globalThis.fetch}export class RpcClient{#t;#e;#r;constructor(t,e={}){this.#t=t,this.#e={"Content-Type":"application/json",...e},this.#r=1e3*Date.now()+Math.floor(1e3*Math.random())}async call(e,r={},s=null,i={}){null===s&&(s=++this.#r);const n={jsonrpc:"2.0",method:e,params:r,id:s};try{const e=await t(this.#t,{method:"POST",headers:{...this.#e,...i},body:JSON.stringify(n)});if(!e.ok)throw new Error(`HTTP Error: ${e.status} ${e.statusText}`);const r=await e.json();if(r.error)throw r.error;return this.deserializeBigIntsAndDates(r.result)}catch(t){throw console.error("RPC call failed:",t),t}}async notify(e,r={},s={}){const i={jsonrpc:"2.0",method:e,params:r};try{const e=await t(this.#t,{method:"POST",headers:{...this.#e,...s},body:JSON.stringify(i)});if(!e.ok)throw new Error(`HTTP Error: ${e.status} ${e.statusText}`)}catch(t){throw console.error("RPC notification failed:",t),t}}async batch(e,r={}){const s=e.map(t=>{const e={jsonrpc:"2.0",method:t.method,params:t.params||{}};return t.notification||(e.id=void 0!==t.id?t.id:++this.#r),e});try{const e=await t(this.#t,{method:"POST",headers:{...this.#e,...r},body:JSON.stringify(s)});if(!e.ok)throw new Error(`HTTP Error: ${e.status} ${e.statusText}`);const i=await e.json();if(Array.isArray(i))return i.map(t=>{if(t.error)throw t.error;return this.deserializeBigIntsAndDates(t.result)});if(i.error)throw i.error;return[this.deserializeBigIntsAndDates(i.result)]}catch(t){throw console.error("RPC batch call failed:",t),t}}serializeBigIntsAndDates(t){if("bigint"==typeof t)return t.toString();if(Array.isArray(t))return t.map(t=>this.serializeBigIntsAndDates(t));if(t instanceof Date)return t.toISOString();if(t&&"object"==typeof t){const e={};for(const[r,s]of Object.entries(t))e[r]=this.serializeBigIntsAndDates(s);return e}return t}deserializeBigIntsAndDates(t){const e=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/;if("string"==typeof t){if(/^-?\d+n?$/.test(t))return BigInt(t.replace(/n$/,""));if(e.test(t)){const e=new Date(t);if(!isNaN(e.getTime()))return e}}return Array.isArray(t)?t.map(t=>this.deserializeBigIntsAndDates(t)):t&&"object"==typeof t?Object.fromEntries(Object.entries(t).map(([t,e])=>[t,this.deserializeBigIntsAndDates(e)])):t}}
1
+ let t;if("undefined"==typeof BigInt||BigInt.prototype.toJSON||(BigInt.prototype.toJSON=function(){return this.toString()}),"undefined"!=typeof fetch)t=fetch;else try{const{default:e}=await import("node-fetch");t=e}catch(e){if("undefined"==typeof globalThis||!globalThis.fetch)throw new Error("fetch is not available. Please install node-fetch or use Node.js 18+");t=globalThis.fetch}export class RpcClient{#t;#e;#r;#i;constructor(t,e={},r={}){if(this.#t=t,this.#e={"Content-Type":"application/json",...e},this.#r=1e3*Date.now()+Math.floor(1e3*Math.random()),this.#i={},"undefined"!=typeof process&&process.env&&!1===r.rejectUnauthorized&&"undefined"!=typeof require)try{const t=require("https");this.#i.agent=new t.Agent({rejectUnauthorized:!1})}catch(t){import("https").then(t=>{this.#i.agent=new t.Agent({rejectUnauthorized:!1})}).catch(()=>{})}}async call(e,r={},i=null,s={}){null===i&&(i=++this.#r);const n={jsonrpc:"2.0",method:e,params:r,id:i};try{const e=await t(this.#t,{method:"POST",headers:{...this.#e,...s},body:JSON.stringify(n),...this.#i});if(!e.ok)throw new Error(`HTTP Error: ${e.status} ${e.statusText}`);const r=await e.json();if(r.error)throw r.error;return this.deserializeBigIntsAndDates(r.result)}catch(t){throw console.error("RPC call failed:",t),t}}async notify(e,r={},i={}){const s={jsonrpc:"2.0",method:e,params:r};try{const e=await t(this.#t,{method:"POST",headers:{...this.#e,...i},body:JSON.stringify(s),...this.#i});if(!e.ok)throw new Error(`HTTP Error: ${e.status} ${e.statusText}`)}catch(t){throw console.error("RPC notification failed:",t),t}}async batch(e,r={}){const i=e.map(t=>{const e={jsonrpc:"2.0",method:t.method,params:t.params||{}};return t.notification||(e.id=void 0!==t.id?t.id:++this.#r),e});try{const e=await t(this.#t,{method:"POST",headers:{...this.#e,...r},body:JSON.stringify(i),...this.#i});if(!e.ok)throw new Error(`HTTP Error: ${e.status} ${e.statusText}`);const s=await e.json();if(Array.isArray(s))return s.map(t=>{if(t.error)throw t.error;return this.deserializeBigIntsAndDates(t.result)});if(s.error)throw s.error;return[this.deserializeBigIntsAndDates(s.result)]}catch(t){throw console.error("RPC batch call failed:",t),t}}serializeBigIntsAndDates(t){if("bigint"==typeof t)return t.toString();if(Array.isArray(t))return t.map(t=>this.serializeBigIntsAndDates(t));if(t instanceof Date)return t.toISOString();if(t&&"object"==typeof t){const e={};for(const[r,i]of Object.entries(t))e[r]=this.serializeBigIntsAndDates(i);return e}return t}deserializeBigIntsAndDates(t){const e=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/;if("string"==typeof t){if(/^-?\d+n?$/.test(t))return BigInt(t.replace(/n$/,""));if(e.test(t)){const e=new Date(t);if(!isNaN(e.getTime()))return e}}return Array.isArray(t)?t.map(t=>this.deserializeBigIntsAndDates(t)):t&&"object"==typeof t?Object.fromEntries(Object.entries(t).map(([t,e])=>[t,this.deserializeBigIntsAndDates(e)])):t}}
@@ -34,12 +34,15 @@ export class RpcClient {
34
34
  #endpoint;
35
35
  #defaultHeaders;
36
36
  #requestId;
37
+ #fetchOptions;
37
38
 
38
39
  /**
39
40
  * @param {string} endpoint The JSON-RPC endpoint URL.
40
41
  * @param {Object} [defaultHeaders={}] Optional default headers to include in requests.
42
+ * @param {Object} [options={}] Optional configuration options.
43
+ * @param {boolean} [options.rejectUnauthorized=true] Whether to reject unauthorized SSL certificates. Set to false for development with self-signed certificates.
41
44
  */
42
- constructor(endpoint, defaultHeaders = {}) {
45
+ constructor(endpoint, defaultHeaders = {}, options = {}) {
43
46
  this.#endpoint = endpoint;
44
47
  this.#defaultHeaders = {
45
48
  "Content-Type": "application/json", // Default header
@@ -47,6 +50,31 @@ export class RpcClient {
47
50
  };
48
51
  // Initialize request ID counter with timestamp + random component for uniqueness
49
52
  this.#requestId = Date.now() * 1000 + Math.floor(Math.random() * 1000);
53
+
54
+ // Store fetch options for Node.js environments
55
+ this.#fetchOptions = {};
56
+
57
+ // Handle SSL certificate validation option for Node.js
58
+ if (typeof process !== 'undefined' && process.env && options.rejectUnauthorized === false) {
59
+ // Set up agent for Node.js fetch implementations
60
+ if (typeof require !== 'undefined') {
61
+ try {
62
+ const https = require('https');
63
+ this.#fetchOptions.agent = new https.Agent({
64
+ rejectUnauthorized: false
65
+ });
66
+ } catch (e) {
67
+ // For ES modules, we'll try dynamic import
68
+ import('https').then(httpsModule => {
69
+ this.#fetchOptions.agent = new httpsModule.Agent({
70
+ rejectUnauthorized: false
71
+ });
72
+ }).catch(() => {
73
+ // Ignore if https module is not available
74
+ });
75
+ }
76
+ }
77
+ }
50
78
  }
51
79
 
52
80
  /**
@@ -80,6 +108,7 @@ export class RpcClient {
80
108
  // Since we have BigInt.prototype.toJSON, we don't need manual serialization
81
109
  // JSON.stringify will handle BigInt and Date automatically
82
110
  body: JSON.stringify(requestBody),
111
+ ...this.#fetchOptions, // Include any additional fetch options (e.g., SSL settings)
83
112
  });
84
113
 
85
114
  if (!response.ok) {
@@ -121,6 +150,7 @@ export class RpcClient {
121
150
  ...overrideHeaders,
122
151
  },
123
152
  body: JSON.stringify(requestBody),
153
+ ...this.#fetchOptions, // Include any additional fetch options (e.g., SSL settings)
124
154
  });
125
155
 
126
156
  if (!response.ok) {
@@ -162,6 +192,7 @@ export class RpcClient {
162
192
  ...overrideHeaders,
163
193
  },
164
194
  body: JSON.stringify(requestBodies),
195
+ ...this.#fetchOptions, // Include any additional fetch options (e.g., SSL settings)
165
196
  });
166
197
 
167
198
  if (!response.ok) {
package/src/index.d.ts CHANGED
@@ -90,8 +90,20 @@ declare class RpcClient {
90
90
  *
91
91
  * @param endpoint - JSON-RPC endpoint URL
92
92
  * @param defaultHeaders - Default headers to include in requests
93
+ * @param options - Configuration options
94
+ * @param options.rejectUnauthorized - Whether to reject unauthorized SSL certificates (default: true). Set to false for development with self-signed certificates.
95
+ * @param options.ca - Buffer or string of a custom CA certificate to trust (for self-signed CA)
96
+ * @param options.agent - Custom https.Agent instance (advanced usage)
93
97
  */
94
- constructor(endpoint: string, defaultHeaders?: Record<string, string>);
98
+ constructor(
99
+ endpoint: string,
100
+ defaultHeaders?: Record<string, string>,
101
+ options?: {
102
+ rejectUnauthorized?: boolean;
103
+ ca?: string | Buffer;
104
+ agent?: any;
105
+ }
106
+ );
95
107
 
96
108
  /**
97
109
  * Make a JSON-RPC call to the server.