rpc-express-toolkit 2.0.1 → 2.1.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,33 @@ 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:
520
+
521
+ ```javascript
522
+ const { RpcClient } = require('rpc-express-toolkit');
523
+
524
+ // Basic client
525
+ const client = new RpcClient('https://api.example.com/rpc');
526
+
527
+ // Client with custom headers
528
+ const clientWithHeaders = new RpcClient('https://api.example.com/rpc', {
529
+ 'Authorization': 'Bearer your-token',
530
+ 'X-API-Key': 'your-api-key',
531
+ 'User-Agent': 'MyApp/1.0'
532
+ });
533
+
534
+ // Client for development with self-signed certificates
535
+ const devClient = new RpcClient('https://localhost:3000/api', {
536
+ 'Authorization': 'Bearer dev-token'
537
+ }, {
538
+ rejectUnauthorized: false // Only for development!
539
+ });
540
+ ```
541
+
542
+ **⚠️ Security Warning**: Only use `rejectUnauthorized: false` in development environments with self-signed certificates. Never use this in production as it disables SSL certificate validation and makes your application vulnerable to man-in-the-middle attacks.
543
+
517
544
  ### Built-in Middlewares
518
545
 
519
546
  ```javascript
@@ -721,10 +748,6 @@ This project uses ESLint and Prettier for code formatting. Run `npm run lint` to
721
748
 
722
749
  ## Security
723
750
 
724
- ### Vulnerability Reporting
725
-
726
- If you discover a security vulnerability, please send an email to security@example.com. All security vulnerabilities will be promptly addressed.
727
-
728
751
  ### Best Practices
729
752
 
730
753
  - Always validate input parameters using schema validation
@@ -0,0 +1,75 @@
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
+ // Example 2: Development client for self-signed certificates
22
+ console.log('2. Development Client (bypasses SSL validation):');
23
+ const developmentClient = new RpcClient('https://localhost:3000/api', {
24
+ 'Authorization': 'Bearer dev-token',
25
+ 'X-Environment': 'development'
26
+ }, {
27
+ rejectUnauthorized: false // Only for development!
28
+ });
29
+ console.log(' ✓ Created client that bypasses SSL certificate validation');
30
+ console.log(' ⚠️ WARNING: Only use this for development with self-signed certificates!\n');
31
+
32
+ // Example 3: Demonstrate how to choose configuration based on environment
33
+ console.log('3. Environment-based Configuration:');
34
+
35
+ const isDevelopment = process.env.NODE_ENV === 'development';
36
+ const baseUrl = isDevelopment ? 'https://localhost:3000/api' : 'https://api.production.com/rpc';
37
+
38
+ const environmentClient = new RpcClient(baseUrl, {
39
+ 'Authorization': `Bearer ${process.env.API_TOKEN || 'default-token'}`,
40
+ 'X-Environment': process.env.NODE_ENV || 'development'
41
+ }, {
42
+ // Only bypass SSL validation in development
43
+ ...(isDevelopment && { rejectUnauthorized: false })
44
+ });
45
+
46
+ console.log(` ✓ Created client for ${isDevelopment ? 'development' : 'production'} environment`);
47
+ console.log(` ✓ SSL validation: ${isDevelopment ? 'disabled (dev only)' : 'enabled (secure)'}\n`);
48
+
49
+ // Example usage (commented out since we don't have a real server)
50
+ /*
51
+ try {
52
+ // This would work with a real server
53
+ const result = await developmentClient.call('ping', {});
54
+ console.log('Server response:', result);
55
+ } catch (error) {
56
+ console.error('Error calling server:', error.message);
57
+ }
58
+ */
59
+
60
+ console.log('4. Best Practices:');
61
+ console.log(' ✓ Always use strict SSL validation in production');
62
+ console.log(' ✓ Only disable SSL validation for development with self-signed certificates');
63
+ console.log(' ✓ Use environment variables to control SSL behavior');
64
+ console.log(' ✓ Never commit rejectUnauthorized: false to production code');
65
+ console.log(' ✓ Consider using proper development certificates instead of disabling validation\n');
66
+
67
+ console.log('=== Example Complete ===');
68
+ }
69
+
70
+ // Run the example if this file is executed directly
71
+ if (require.main === module) {
72
+ demonstrateSSLOptions().catch(console.error);
73
+ }
74
+
75
+ module.exports = { demonstrateSSLOptions };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rpc-express-toolkit",
3
- "version": "2.0.1",
3
+ "version": "2.1.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,24 @@ 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
+
56
+ // Handle SSL certificate validation option for Node.js
57
+ if (typeof process !== 'undefined' && process.env && options.rejectUnauthorized === false) {
58
+ // Set up agent for Node.js fetch implementations
59
+ if (typeof require !== 'undefined') {
60
+ try {
61
+ const https = require('https');
62
+ this.#fetchOptions.agent = new https.Agent({
63
+ rejectUnauthorized: false
64
+ });
65
+ } catch (e) {
66
+ // Ignore if https module is not available
67
+ }
68
+ }
69
+ }
49
70
  }
50
71
 
51
72
  /**
@@ -85,6 +106,7 @@ class RpcClient {
85
106
  // Since we have BigInt.prototype.toJSON, we don't need manual serialization
86
107
  // JSON.stringify will handle BigInt and Date automatically
87
108
  body: JSON.stringify(requestBody),
109
+ ...this.#fetchOptions, // Include any additional fetch options (e.g., SSL settings)
88
110
  });
89
111
 
90
112
  if (!response.ok) {
@@ -136,6 +158,7 @@ class RpcClient {
136
158
  ...overrideHeaders,
137
159
  },
138
160
  body: JSON.stringify(batchRequests),
161
+ ...this.#fetchOptions, // Include any additional fetch options (e.g., SSL settings)
139
162
  });
140
163
 
141
164
  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&&!1===r.rejectUnauthorized&&"undefined"!=typeof require)try{const t=require("https");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,10 @@ 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.
93
95
  */
94
- constructor(endpoint: string, defaultHeaders?: Record<string, string>);
96
+ constructor(endpoint: string, defaultHeaders?: Record<string, string>, options?: { rejectUnauthorized?: boolean });
95
97
 
96
98
  /**
97
99
  * Make a JSON-RPC call to the server.