rpc-express-toolkit 2.2.0 → 2.3.1

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
@@ -91,6 +91,7 @@ app.listen(3000, () => {
91
91
  });
92
92
  ```
93
93
 
94
+
94
95
  ### Client Usage
95
96
 
96
97
  ```javascript
@@ -108,6 +109,22 @@ const client = new RpcClient('/api');
108
109
  const user = await client.call('getUser', { userId: 123 });
109
110
  ```
110
111
 
112
+
113
+ #### ⚠️ SSL and self-signed certificates in development (Node.js)
114
+
115
+ If you need to connect to a server with a self-signed certificate during development, set the following environment variable **before** starting your Node.js process:
116
+
117
+ ```js
118
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
119
+ ```
120
+
121
+ **Warning:**
122
+ - This disables SSL certificate validation for all HTTPS requests in the Node.js process.
123
+ - Use this only in development environments!
124
+ - Never commit or enable this setting in production.
125
+
126
+ Advanced options like `agent` and `ca` have been removed for simplicity and compatibility with modern Node.js. This is the only universal and reliable way to bypass SSL validation for development with self-signed certificates.
127
+
111
128
  ## Advanced Usage
112
129
 
113
130
  ### Configuration Options
@@ -321,14 +338,26 @@ new RpcClient(baseUrl, options)
321
338
 
322
339
  #### Methods
323
340
 
341
+
324
342
  ##### `call(method, params, options)`
325
343
 
326
344
  Make a single RPC call.
327
345
 
328
346
  ```javascript
329
- const result = await client.call('add', { a: 1, b: 2 });
347
+ // If the method does not require parameters, you can omit the params argument:
348
+ const result = await client.call('ping');
349
+
350
+ // If you pass params as undefined or null, the field will be omitted from the JSON-RPC payload (per spec):
351
+ await client.call('ping', undefined);
352
+ await client.call('ping', null);
353
+
354
+ // If you pass an object or array, it will be sent as params:
355
+ await client.call('add', { a: 1, b: 2 });
330
356
  ```
331
357
 
358
+ > **Note:**
359
+ > The `params` field is omitted from the JSON-RPC request if you pass `undefined` or `null`, as required by the JSON-RPC 2.0 specification. If you pass an object or array, it will be included as `params`.
360
+
332
361
  ##### `batch(requests)`
333
362
 
334
363
  Make batch RPC calls.
@@ -19,29 +19,20 @@ async function demonstrateSSLOptions() {
19
19
  console.log(' ✓ Created client with strict SSL validation (default)\n');
20
20
 
21
21
 
22
- // Example 2: Development client for self-signed certificates (bypasses ALL SSL validation)
22
+
23
+ // Example 2: Development client per self-signed certificates (bypassa TUTTA la validazione SSL)
23
24
  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');
25
+ console.log(' ⚠️ Per bypassare la validazione SSL in Node.js, imposta PRIMA di avviare il client:');
26
+ console.log(' process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";');
27
+ console.log(' ✓ Tutte le richieste accetteranno certificati self-signed (solo per sviluppo!)\n');
32
28
 
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');
29
+ // Example 3: Best practice
30
+ console.log('3. Best Practices:');
31
+ console.log(' ✓ Usa sempre la validazione SSL in produzione');
32
+ console.log(' ✓ Disabilita la validazione solo per sviluppo con self-signed');
33
+ console.log(' ✓ Usa variabili d’ambiente per controllare il comportamento SSL');
34
+ console.log(' ✓ Non committare mai process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0" in produzione');
35
+ console.log(' ✓ Considera l’uso di certificati di sviluppo validi invece di disabilitare la validazione\n');
45
36
 
46
37
  // Example 4: Demonstrate how to choose configuration based on environment
47
38
  console.log('4. Environment-based Configuration:');
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rpc-express-toolkit",
3
- "version": "2.2.0",
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.",
3
+ "version": "2.3.1",
4
+ "description": "Enterprise-ready JSON-RPC 2.0 toolkit for Express.js with simplified APIs, structured logging, middleware system, schema validation, comprehensive BigInt/Date support. Semplificato: gestione SSL avanzata rimossa, vedi README.",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
7
7
  "files": [
@@ -39,7 +39,11 @@
39
39
  "validation",
40
40
  "bigint",
41
41
  "enterprise",
42
- "nodejs"
42
+ "nodejs",
43
+ "ssl",
44
+ "self-signed",
45
+ "secure",
46
+ "fetch"
43
47
  ],
44
48
  "author": "Nicola Carpanese <nicola.carpanese@gmail.com>",
45
49
  "license": "MIT",
@@ -52,29 +52,10 @@ class RpcClient {
52
52
 
53
53
  // Store fetch options for Node.js environments
54
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
- }
55
+ // SSL validation: la gestione avanzata (agent/ca) è rimossa per semplicità e compatibilità.
56
+ // Per bypassare i certificati self-signed in sviluppo, imposta:
57
+ // process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
58
+ // Vedi README per dettagli e best practice.
78
59
  }
79
60
 
80
61
  /**
@@ -96,13 +77,16 @@ class RpcClient {
96
77
  async call(method, params = {}, id = undefined, overrideHeaders = {}) {
97
78
  // Auto-generate ID if not provided (null means notification)
98
79
  const requestId = id === undefined ? this.#generateId() : id;
99
-
80
+
81
+ // Costruisci il payload secondo lo standard: params omesso se undefined/null
100
82
  const requestBody = {
101
83
  jsonrpc: "2.0",
102
84
  method,
103
- params,
104
85
  id: requestId,
105
86
  };
87
+ if (params !== undefined && params !== null) {
88
+ requestBody.params = params;
89
+ }
106
90
 
107
91
  try {
108
92
  const response = await fetchFn(this.#endpoint, {
@@ -151,12 +135,17 @@ class RpcClient {
151
135
  * @returns {Promise<Array<any>>} Array of results in the same order as requests.
152
136
  */
153
137
  async batch(requests, overrideHeaders = {}) {
154
- const batchRequests = requests.map(req => ({
155
- jsonrpc: "2.0",
156
- method: req.method,
157
- params: req.params || {},
158
- id: req.id !== undefined ? req.id : this.#generateId()
159
- }));
138
+ const batchRequests = requests.map(req => {
139
+ const obj = {
140
+ jsonrpc: "2.0",
141
+ method: req.method,
142
+ id: req.id !== undefined ? req.id : this.#generateId()
143
+ };
144
+ if (req.params !== undefined && req.params !== null) {
145
+ obj.params = req.params;
146
+ }
147
+ return obj;
148
+ });
160
149
 
161
150
  try {
162
151
  const response = await fetchFn(this.#endpoint, {
@@ -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&&"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
+ 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={}){this.#t=t,this.#e={"Content-Type":"application/json",...e},this.#r=1e3*Date.now()+Math.floor(1e3*Math.random()),this.#i={}}#s(){return++this.#r}async call(t,e={},r=void 0,i={}){const s={jsonrpc:"2.0",method:t,id:void 0===r?this.#s():r};null!=e&&(s.params=e);try{const t=await fetchFn(this.#t,{method:"POST",headers:{...this.#e,...i},body:JSON.stringify(s),...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=>{const e={jsonrpc:"2.0",method:t.method,id:void 0!==t.id?t.id:this.#s()};return void 0!==t.params&&null!==t.params&&(e.params=t.params),e});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;#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}}
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;#s;constructor(t,e={},r={}){this.#t=t,this.#e={"Content-Type":"application/json",...e},this.#r=1e3*Date.now()+Math.floor(1e3*Math.random()),this.#s={}}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),...this.#s});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),...this.#s});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),...this.#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}}
@@ -53,28 +53,10 @@ export class RpcClient {
53
53
 
54
54
  // Store fetch options for Node.js environments
55
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
- }
56
+ // SSL validation: la gestione avanzata (agent/ca) è rimossa per semplicità e compatibilità.
57
+ // Per bypassare i certificati self-signed in sviluppo, imposta:
58
+ // process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
59
+ // Vedi README per dettagli e best practice.
78
60
  }
79
61
 
80
62
  /**
package/src/index.d.ts CHANGED
@@ -99,9 +99,7 @@ declare class RpcClient {
99
99
  endpoint: string,
100
100
  defaultHeaders?: Record<string, string>,
101
101
  options?: {
102
- rejectUnauthorized?: boolean;
103
- ca?: string | Buffer;
104
- agent?: any;
102
+ // Opzioni SSL avanzate rimosse: usa process.env.NODE_TLS_REJECT_UNAUTHORIZED per sviluppo
105
103
  }
106
104
  );
107
105