rpc-express-toolkit 2.1.0 → 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
@@ -516,30 +516,40 @@ logger.error('Database error', {
516
516
 
517
517
  ### Client Configuration
518
518
 
519
- The `RpcClient` supports various configuration options for different environments:
519
+ The `RpcClient` supports various configuration options for different environments, **inclusa la gestione di certificati self-signed**:
520
520
 
521
521
  ```javascript
522
+ const fs = require('fs');
522
523
  const { RpcClient } = require('rpc-express-toolkit');
523
524
 
524
525
  // Basic client
525
526
  const client = new RpcClient('https://api.example.com/rpc');
526
527
 
527
- // Client with custom headers
528
+ // Client con header personalizzati
528
529
  const clientWithHeaders = new RpcClient('https://api.example.com/rpc', {
529
530
  'Authorization': 'Bearer your-token',
530
531
  'X-API-Key': 'your-api-key',
531
532
  'User-Agent': 'MyApp/1.0'
532
533
  });
533
534
 
534
- // Client for development with self-signed certificates
535
+ // Client per sviluppo con certificato self-signed (accetta qualsiasi certificato, NON usare in produzione)
535
536
  const devClient = new RpcClient('https://localhost:3000/api', {
536
537
  'Authorization': 'Bearer dev-token'
537
538
  }, {
538
- rejectUnauthorized: false // Only for development!
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 })
539
549
  });
540
550
  ```
541
551
 
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.
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.
543
553
 
544
554
  ### Built-in Middlewares
545
555
 
@@ -18,33 +18,41 @@ async function demonstrateSSLOptions() {
18
18
  });
19
19
  console.log(' ✓ Created client with strict SSL validation (default)\n');
20
20
 
21
- // Example 2: Development client for self-signed certificates
21
+
22
+ // Example 2: Development client for self-signed certificates (bypasses ALL SSL validation)
22
23
  console.log('2. Development Client (bypasses SSL validation):');
23
24
  const developmentClient = new RpcClient('https://localhost:3000/api', {
24
25
  'Authorization': 'Bearer dev-token',
25
26
  'X-Environment': 'development'
26
27
  }, {
27
- rejectUnauthorized: false // Only for development!
28
+ rejectUnauthorized: false // Solo per sviluppo!
28
29
  });
29
30
  console.log(' ✓ Created client that bypasses SSL certificate validation');
30
31
  console.log(' ⚠️ WARNING: Only use this for development with self-signed certificates!\n');
31
32
 
32
- // Example 3: Demonstrate how to choose configuration based on environment
33
- console.log('3. Environment-based Configuration:');
34
-
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:');
35
48
  const isDevelopment = process.env.NODE_ENV === 'development';
36
49
  const baseUrl = isDevelopment ? 'https://localhost:3000/api' : 'https://api.production.com/rpc';
37
-
38
50
  const environmentClient = new RpcClient(baseUrl, {
39
51
  'Authorization': `Bearer ${process.env.API_TOKEN || 'default-token'}`,
40
52
  'X-Environment': process.env.NODE_ENV || 'development'
41
- }, {
42
- // Only bypass SSL validation in development
43
- ...(isDevelopment && { rejectUnauthorized: false })
44
- });
45
-
53
+ }, isDevelopment ? { ca } : {});
46
54
  console.log(` ✓ Created client for ${isDevelopment ? 'development' : 'production'} environment`);
47
- console.log(` ✓ SSL validation: ${isDevelopment ? 'disabled (dev only)' : 'enabled (secure)'}\n`);
55
+ console.log(` ✓ SSL validation: ${isDevelopment ? 'custom CA (dev only)' : 'enabled (secure)'}\n`);
48
56
 
49
57
  // Example usage (commented out since we don't have a real server)
50
58
  /*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rpc-express-toolkit",
3
- "version": "2.1.0",
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",
@@ -52,19 +52,27 @@ class RpcClient {
52
52
 
53
53
  // Store fetch options for Node.js environments
54
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');
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
62
70
  this.#fetchOptions.agent = new https.Agent({
63
71
  rejectUnauthorized: false
64
72
  });
65
- } catch (e) {
66
- // Ignore if https module is not available
67
73
  }
74
+ } catch (e) {
75
+ // Ignore if https module is not available
68
76
  }
69
77
  }
70
78
  }
@@ -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;#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
+ 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;
package/src/index.d.ts CHANGED
@@ -92,8 +92,18 @@ declare class RpcClient {
92
92
  * @param defaultHeaders - Default headers to include in requests
93
93
  * @param options - Configuration options
94
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)
95
97
  */
96
- constructor(endpoint: string, defaultHeaders?: Record<string, string>, options?: { rejectUnauthorized?: boolean });
98
+ constructor(
99
+ endpoint: string,
100
+ defaultHeaders?: Record<string, string>,
101
+ options?: {
102
+ rejectUnauthorized?: boolean;
103
+ ca?: string | Buffer;
104
+ agent?: any;
105
+ }
106
+ );
97
107
 
98
108
  /**
99
109
  * Make a JSON-RPC call to the server.