rpc-express-toolkit 2.3.2 → 3.0.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.
@@ -1,14 +1,16 @@
1
1
  /**
2
2
  * JSON-RPC 2.0 Client for browser and Node.js environments.
3
3
  * Handles BigInt and Date serialization/deserialization automatically.
4
- *
4
+ *
5
5
  * Note: We extend BigInt.prototype.toJSON to handle automatic serialization
6
6
  * when JSON.stringify() is called, as BigInt doesn't have native JSON support.
7
7
  */
8
8
 
9
9
  // Extend BigInt prototype to handle JSON serialization
10
10
  if (typeof BigInt !== 'undefined' && !BigInt.prototype.toJSON) {
11
- BigInt.prototype.toJSON = function() { return this.toString(); };
11
+ BigInt.prototype.toJSON = function () {
12
+ return this.toString();
13
+ };
12
14
  }
13
15
 
14
16
  // Polyfill for Node.js environments that don't have fetch
@@ -24,7 +26,9 @@ if (typeof fetch !== 'undefined') {
24
26
  if (typeof globalThis !== 'undefined' && globalThis.fetch) {
25
27
  fetchFn = globalThis.fetch;
26
28
  } else {
27
- throw new Error('fetch is not available. Please install node-fetch or use Node.js 18+');
29
+ throw new Error(
30
+ 'fetch is not available. Please install node-fetch or use Node.js 18+'
31
+ );
28
32
  }
29
33
  }
30
34
  }
@@ -34,22 +38,34 @@ class RpcClient {
34
38
  #defaultHeaders;
35
39
  #requestId;
36
40
  #fetchOptions;
41
+ #options;
37
42
 
38
43
  /**
39
44
  * @param {string} endpoint The JSON-RPC endpoint URL.
40
45
  * @param {Object} [defaultHeaders={}] Optional default headers to include in requests.
41
46
  * @param {Object} [options={}] Optional configuration options.
42
47
  * @param {boolean} [options.rejectUnauthorized=true] Whether to reject unauthorized SSL certificates. Set to false for development with self-signed certificates.
48
+ * @param {boolean} [options.safeStringEnabled=true] Whether to prefix strings with 'S:' to avoid confusion with BigInt values.
49
+ * @param {boolean} [options.safeDateEnabled=true] Whether to prefix dates with 'D:' to avoid confusion with ISO string values.
43
50
  */
44
51
  constructor(endpoint, defaultHeaders = {}, options = {}) {
45
52
  this.#endpoint = endpoint;
46
53
  this.#defaultHeaders = {
47
- "Content-Type": "application/json", // Default header
54
+ 'Content-Type': 'application/json', // Default header
48
55
  ...defaultHeaders, // Merge with user-provided defaults
49
56
  };
50
57
  // Initialize request ID counter with timestamp + random component for uniqueness
51
58
  this.#requestId = Date.now() * 1000 + Math.floor(Math.random() * 1000);
52
-
59
+
60
+ // Store options
61
+ this.#options = {
62
+ safeStringEnabled: options.safeStringEnabled !== false, // Default true
63
+ safeDateEnabled: options.safeDateEnabled !== false, // Default true
64
+ warnOnUnsafeString: options.warnOnUnsafeString !== false, // Default true
65
+ warnOnUnsafeDate: options.warnOnUnsafeDate !== false, // Default true
66
+ ...options
67
+ };
68
+
53
69
  // Store fetch options for Node.js environments
54
70
  this.#fetchOptions = {};
55
71
  // SSL validation: advanced options (agent/ca) have been removed for simplicity and compatibility.
@@ -80,19 +96,23 @@ class RpcClient {
80
96
 
81
97
  // Build the payload according to the spec: params omitted if undefined/null
82
98
  const requestBody = {
83
- jsonrpc: "2.0",
99
+ jsonrpc: '2.0',
84
100
  method,
85
101
  id: requestId,
86
102
  };
87
103
  if (params !== undefined && params !== null) {
88
- requestBody.params = params;
104
+ // Serialize parameters to handle BigInt and safe strings
105
+ requestBody.params = this.serializeBigIntsAndDates(params);
89
106
  }
90
107
 
91
108
  try {
92
109
  const response = await fetchFn(this.#endpoint, {
93
- method: "POST",
110
+ method: 'POST',
94
111
  headers: {
95
112
  ...this.#defaultHeaders,
113
+ // Add safe options headers so server knows what client expects
114
+ 'X-RPC-SafeString-Enabled': this.#options.safeStringEnabled ? 'true' : 'false',
115
+ 'X-RPC-SafeDate-Enabled': this.#options.safeDateEnabled ? 'true' : 'false',
96
116
  ...overrideHeaders,
97
117
  },
98
118
  // Since we have BigInt.prototype.toJSON, we don't need manual serialization
@@ -102,17 +122,31 @@ class RpcClient {
102
122
  });
103
123
 
104
124
  if (!response.ok) {
105
- throw new Error(`HTTP Error: ${response.status} ${response.statusText}`);
125
+ throw new Error(
126
+ `HTTP Error: ${response.status} ${response.statusText}`
127
+ );
106
128
  }
107
129
 
108
130
  const responseBody = await response.json();
109
131
 
110
- if (responseBody.error) { throw responseBody.error; }
132
+ if (responseBody.error) {
133
+ throw responseBody.error;
134
+ }
135
+
136
+ // Check server's safe options from response headers
137
+ const serverSafeStringEnabled = response.headers.get('X-RPC-SafeString-Enabled') === 'true';
138
+ const serverSafeDateEnabled = response.headers.get('X-RPC-SafeDate-Enabled') === 'true';
111
139
 
112
- // Convert back BigInts and Dates in the result
113
- return this.deserializeBigIntsAndDates(responseBody.result);
140
+ // Create deserialization options based on server's configuration
141
+ const deserializationOptions = {
142
+ safeStringEnabled: serverSafeStringEnabled,
143
+ safeDateEnabled: serverSafeDateEnabled
144
+ };
145
+
146
+ // Convert back BigInts and Dates in the result using server's options
147
+ return this.deserializeBigIntsAndDates(responseBody.result, deserializationOptions);
114
148
  } catch (error) {
115
- console.error("RPC call failed:", error);
149
+ console.error('RPC call failed:', error);
116
150
  throw error;
117
151
  }
118
152
  }
@@ -135,23 +169,27 @@ class RpcClient {
135
169
  * @returns {Promise<Array<any>>} Array of results in the same order as requests.
136
170
  */
137
171
  async batch(requests, overrideHeaders = {}) {
138
- const batchRequests = requests.map(req => {
172
+ const batchRequests = requests.map((req) => {
139
173
  const obj = {
140
- jsonrpc: "2.0",
174
+ jsonrpc: '2.0',
141
175
  method: req.method,
142
- id: req.id !== undefined ? req.id : this.#generateId()
176
+ id: req.id !== undefined ? req.id : this.#generateId(),
143
177
  };
144
178
  if (req.params !== undefined && req.params !== null) {
145
- obj.params = req.params;
179
+ // Serialize parameters to handle BigInt and safe strings
180
+ obj.params = this.serializeBigIntsAndDates(req.params);
146
181
  }
147
182
  return obj;
148
183
  });
149
184
 
150
185
  try {
151
186
  const response = await fetchFn(this.#endpoint, {
152
- method: "POST",
187
+ method: 'POST',
153
188
  headers: {
154
189
  ...this.#defaultHeaders,
190
+ // Add safe options headers so server knows what client expects
191
+ 'X-RPC-SafeString-Enabled': this.#options.safeStringEnabled ? 'true' : 'false',
192
+ 'X-RPC-SafeDate-Enabled': this.#options.safeDateEnabled ? 'true' : 'false',
155
193
  ...overrideHeaders,
156
194
  },
157
195
  body: JSON.stringify(batchRequests),
@@ -159,28 +197,40 @@ class RpcClient {
159
197
  });
160
198
 
161
199
  if (!response.ok) {
162
- throw new Error(`HTTP Error: ${response.status} ${response.statusText}`);
200
+ throw new Error(
201
+ `HTTP Error: ${response.status} ${response.statusText}`
202
+ );
163
203
  }
164
204
 
165
205
  const responseBody = await response.json();
166
206
 
207
+ // Check server's safe options from response headers
208
+ const serverSafeStringEnabled = response.headers.get('X-RPC-SafeString-Enabled') === 'true';
209
+ const serverSafeDateEnabled = response.headers.get('X-RPC-SafeDate-Enabled') === 'true';
210
+
211
+ // Create deserialization options based on server's configuration
212
+ const deserializationOptions = {
213
+ safeStringEnabled: serverSafeStringEnabled,
214
+ safeDateEnabled: serverSafeDateEnabled
215
+ };
216
+
167
217
  // Handle batch response
168
218
  if (Array.isArray(responseBody)) {
169
- return responseBody.map(res => {
219
+ return responseBody.map((res) => {
170
220
  if (res.error) {
171
221
  throw res.error;
172
222
  }
173
- return this.deserializeBigIntsAndDates(res.result);
223
+ return this.deserializeBigIntsAndDates(res.result, deserializationOptions);
174
224
  });
175
225
  } else {
176
226
  // Single response in batch
177
227
  if (responseBody.error) {
178
228
  throw responseBody.error;
179
229
  }
180
- return [this.deserializeBigIntsAndDates(responseBody.result)];
230
+ return [this.deserializeBigIntsAndDates(responseBody.result, deserializationOptions)];
181
231
  }
182
232
  } catch (error) {
183
- console.error("Batch RPC call failed:", error);
233
+ console.error('Batch RPC call failed:', error);
184
234
  throw error;
185
235
  }
186
236
  }
@@ -192,16 +242,23 @@ class RpcClient {
192
242
  * @returns {any}
193
243
  */
194
244
  serializeBigIntsAndDates(value) {
195
- if (typeof value === "bigint") {
196
- // Convert BigInt to string
197
- return value.toString();
245
+ if (typeof value === 'bigint') {
246
+ // Convert BigInt to string with 'n' suffix for proper deserialization
247
+ return value.toString() + 'n';
248
+ } else if (value instanceof Date) {
249
+ // Convert Date to ISO string with D: prefix if safeDateEnabled
250
+ const isoString = value.toISOString();
251
+ return this.#options.safeDateEnabled ? `D:${isoString}` : isoString;
252
+ } else if (typeof value === 'string') {
253
+ // Add S: prefix if safeStringEnabled is true
254
+ if (this.#options.safeStringEnabled) {
255
+ return 'S:' + value;
256
+ }
257
+ return value;
198
258
  } else if (Array.isArray(value)) {
199
259
  // Recurse into arrays
200
260
  return value.map((v) => this.serializeBigIntsAndDates(v));
201
- } else if (value instanceof Date) {
202
- // Convert Date to ISO string (UTC)
203
- return value.toISOString();
204
- } else if (value && typeof value === "object") {
261
+ } else if (value && typeof value === 'object') {
205
262
  // Recurse into plain objects
206
263
  const result = {};
207
264
  for (const [key, val] of Object.entries(value)) {
@@ -219,24 +276,48 @@ class RpcClient {
219
276
  * and ISO 8601 date strings back to Date objects.
220
277
  *
221
278
  * @param {any} value The value to deserialize.
279
+ * @param {Object} [options] Custom deserialization options (uses client options if not provided).
280
+ * @param {boolean} [options.safeStringEnabled] Whether to expect S: prefixed strings.
281
+ * @param {boolean} [options.safeDateEnabled] Whether to expect D: prefixed dates.
222
282
  * @returns {any}
223
283
  */
224
- deserializeBigIntsAndDates(value) {
284
+ deserializeBigIntsAndDates(value, options = null) {
285
+ // Use provided options or fall back to client options
286
+ const safeStringEnabled = options ? options.safeStringEnabled : this.#options.safeStringEnabled;
287
+ const safeDateEnabled = options ? options.safeDateEnabled : this.#options.safeDateEnabled;
288
+
225
289
  // More comprehensive ISO date regex that handles:
226
290
  // - UTC: 2023-01-01T12:00:00.000Z
227
291
  // - With timezone: 2023-01-01T12:00:00.000+01:00
228
292
  // - Without timezone: 2023-01-01T12:00:00.000 (treated as local)
229
- const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/;
230
-
231
- // 1. Check if it's a string that might be a BigInt or a Date
232
- if (typeof value === "string") {
233
- // BigInt check: matches digits (including negative), optionally ending in "n"
234
- // e.g., "42n", "42", "-42n", "-42"
235
- if (/^-?\d+n?$/.test(value)) {
236
- return BigInt(value.replace(/n$/, ""));
293
+ const ISO_DATE_REGEX =
294
+ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/;
295
+
296
+ // 1. Check if it's a string that might be a BigInt, Date, or safe string
297
+ if (typeof value === 'string') {
298
+ // Safe string check: if safeStringEnabled and starts with S:
299
+ if (safeStringEnabled && value.startsWith('S:')) {
300
+ return value.substring(2); // Remove 'S:' prefix
301
+ }
302
+
303
+ // Safe date check: if safeDateEnabled and starts with D:
304
+ if (safeDateEnabled && value.startsWith('D:')) {
305
+ const isoString = value.substring(2); // Remove 'D:' prefix
306
+ const date = new Date(isoString);
307
+ // Double-check that we got a valid date
308
+ if (!isNaN(date.getTime())) {
309
+ return date;
310
+ }
311
+ }
312
+
313
+ // BigInt check: only convert strings that explicitly end with "n"
314
+ // e.g., "42n", "-42n" but NOT "42", "0123456"
315
+ if (/^-?\d+n$/.test(value)) {
316
+ return BigInt(value.slice(0, -1)); // Remove 'n' and convert
237
317
  }
238
- // Date check: matches an ISO 8601 string
239
- if (ISO_DATE_REGEX.test(value)) {
318
+
319
+ // Date check: matches an ISO 8601 string (only if safeDateEnabled is false)
320
+ if (!safeDateEnabled && ISO_DATE_REGEX.test(value)) {
240
321
  const date = new Date(value);
241
322
  // Ensure it's valid
242
323
  if (!isNaN(date.getTime())) {
@@ -247,15 +328,15 @@ class RpcClient {
247
328
 
248
329
  // 2. If it's an array, handle each element
249
330
  if (Array.isArray(value)) {
250
- return value.map((v) => this.deserializeBigIntsAndDates(v));
331
+ return value.map((v) => this.deserializeBigIntsAndDates(v, options));
251
332
  }
252
333
 
253
334
  // 3. If it's a plain object, recurse into each property
254
- if (value && typeof value === "object") {
335
+ if (value && typeof value === 'object') {
255
336
  return Object.fromEntries(
256
337
  Object.entries(value).map(([key, val]) => [
257
338
  key,
258
- this.deserializeBigIntsAndDates(val),
339
+ this.deserializeBigIntsAndDates(val, options),
259
340
  ])
260
341
  );
261
342
  }
@@ -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={}){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
+ 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(e){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{#e;#t;#n;#s;#a;constructor(e,t={},n={}){this.#e=e,this.#t={"Content-Type":"application/json",...t},this.#n=1e3*Date.now()+Math.floor(1e3*Math.random()),this.#a={safeStringEnabled:!1!==n.safeStringEnabled,safeDateEnabled:!1!==n.safeDateEnabled,warnOnUnsafeString:!1!==n.warnOnUnsafeString,warnOnUnsafeDate:!1!==n.warnOnUnsafeDate,...n},this.#s={}}#r(){return++this.#n}async call(e,t,n=void 0,s={}){const a={jsonrpc:"2.0",method:e,id:void 0===n?this.#r():n};null!=t&&(a.params=this.serializeBigIntsAndDates(t));try{const e=await fetchFn(this.#e,{method:"POST",headers:{...this.#t,"X-RPC-SafeString-Enabled":this.#a.safeStringEnabled?"true":"false","X-RPC-SafeDate-Enabled":this.#a.safeDateEnabled?"true":"false",...s},body:JSON.stringify(a),...this.#s});if(!e.ok)throw new Error(`HTTP Error: ${e.status} ${e.statusText}`);const t=await e.json();if(t.error)throw t.error;const n="true"===e.headers.get("X-RPC-SafeString-Enabled"),r={safeStringEnabled:n,safeDateEnabled:"true"===e.headers.get("X-RPC-SafeDate-Enabled")};return this.deserializeBigIntsAndDates(t.result,r)}catch(e){throw console.error("RPC call failed:",e),e}}async notify(e,t={},n={}){await this.call(e,t,null,n)}async batch(e,t={}){const n=e.map(e=>{const t={jsonrpc:"2.0",method:e.method,id:void 0!==e.id?e.id:this.#r()};return void 0!==e.params&&null!==e.params&&(t.params=this.serializeBigIntsAndDates(e.params)),t});try{const e=await fetchFn(this.#e,{method:"POST",headers:{...this.#t,"X-RPC-SafeString-Enabled":this.#a.safeStringEnabled?"true":"false","X-RPC-SafeDate-Enabled":this.#a.safeDateEnabled?"true":"false",...t},body:JSON.stringify(n),...this.#s});if(!e.ok)throw new Error(`HTTP Error: ${e.status} ${e.statusText}`);const s=await e.json(),a="true"===e.headers.get("X-RPC-SafeString-Enabled"),r={safeStringEnabled:a,safeDateEnabled:"true"===e.headers.get("X-RPC-SafeDate-Enabled")};if(Array.isArray(s))return s.map(e=>{if(e.error)throw e.error;return this.deserializeBigIntsAndDates(e.result,r)});if(s.error)throw s.error;return[this.deserializeBigIntsAndDates(s.result,r)]}catch(e){throw console.error("Batch RPC call failed:",e),e}}serializeBigIntsAndDates(e){if("bigint"==typeof e)return e.toString()+"n";if(e instanceof Date){const t=e.toISOString();return this.#a.safeDateEnabled?`D:${t}`:t}if("string"==typeof e)return this.#a.safeStringEnabled?"S:"+e:e;if(Array.isArray(e))return e.map(e=>this.serializeBigIntsAndDates(e));if(e&&"object"==typeof e){const t={};for(const[n,s]of Object.entries(e))t[n]=this.serializeBigIntsAndDates(s);return t}return e}deserializeBigIntsAndDates(e,t=null){const n=t?t.safeStringEnabled:this.#a.safeStringEnabled,s=t?t.safeDateEnabled:this.#a.safeDateEnabled,a=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/;if("string"==typeof e){if(n&&e.startsWith("S:"))return e.substring(2);if(s&&e.startsWith("D:")){const t=e.substring(2),n=new Date(t);if(!isNaN(n.getTime()))return n}if(/^-?\d+n$/.test(e))return BigInt(e.slice(0,-1));if(!s&&a.test(e)){const t=new Date(e);if(!isNaN(t.getTime()))return t}}return Array.isArray(e)?e.map(e=>this.deserializeBigIntsAndDates(e,t)):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).map(([e,n])=>[e,this.deserializeBigIntsAndDates(n,t)])):e}}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;#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}}
1
+ let e;if("undefined"==typeof BigInt||BigInt.prototype.toJSON||(BigInt.prototype.toJSON=function(){return this.toString()}),"undefined"!=typeof fetch)e=fetch;else try{e=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+");e=globalThis.fetch}class t{#e;#t;#s;#r;#a;constructor(e,t={},s={}){this.#e=e,this.#t={"Content-Type":"application/json",...t},this.#s=1e3*Date.now()+Math.floor(1e3*Math.random()),this.#a={safeStringEnabled:!1!==s.safeStringEnabled,safeDateEnabled:!1!==s.safeDateEnabled,...s},this.#r={}}#i(){return++this.#s}async call(t,s,r=void 0,a={}){const i={jsonrpc:"2.0",method:t,id:void 0===r?this.#i():r};null!=s&&(i.params=this.serializeBigIntsAndDates(s));try{const t=await e(this.#e,{method:"POST",headers:{...this.#t,"X-RPC-SafeString-Enabled":this.#a.safeStringEnabled?"true":"false","X-RPC-SafeDate-Enabled":this.#a.safeDateEnabled?"true":"false",...a},body:JSON.stringify(i),...this.#r});if(!t.ok)throw new Error(`HTTP Error: ${t.status} ${t.statusText}`);const s=await t.json();if(s.error)throw s.error;const r="true"===t.headers.get("X-RPC-SafeString-Enabled"),n={safeStringEnabled:r,safeDateEnabled:"true"===t.headers.get("X-RPC-SafeDate-Enabled")};return this.deserializeBigIntsAndDates(s.result,n)}catch(e){throw console.error("RPC call failed:",e),e}}async notify(e,t={},s={}){await this.call(e,t,null,s)}async batch(t,s={}){const r=t.map(e=>{const t={jsonrpc:"2.0",method:e.method,id:void 0!==e.id?e.id:this.#i()};return void 0!==e.params&&null!==e.params&&(t.params=this.serializeBigIntsAndDates(e.params)),t});try{const t=await e(this.#e,{method:"POST",headers:{...this.#t,"X-RPC-SafeString-Enabled":this.#a.safeStringEnabled?"true":"false","X-RPC-SafeDate-Enabled":this.#a.safeDateEnabled?"true":"false",...s},body:JSON.stringify(r),...this.#r});if(!t.ok)throw new Error(`HTTP Error: ${t.status} ${t.statusText}`);const a=await t.json(),i="true"===t.headers.get("X-RPC-SafeString-Enabled"),n={safeStringEnabled:i,safeDateEnabled:"true"===t.headers.get("X-RPC-SafeDate-Enabled")};if(Array.isArray(a))return a.map(e=>{if(e.error)throw e.error;return this.deserializeBigIntsAndDates(e.result,n)});if(a.error)throw a.error;return[this.deserializeBigIntsAndDates(a.result,n)]}catch(e){throw console.error("Batch RPC call failed:",e),e}}serializeBigIntsAndDates(e){if("bigint"==typeof e)return e.toString()+"n";if(e instanceof Date){const t=e.toISOString();return this.#a.safeDateEnabled?`D:${t}`:t}if("string"==typeof e)return this.#a.safeStringEnabled?"S:"+e:e;if(Array.isArray(e))return e.map(e=>this.serializeBigIntsAndDates(e));if(e&&"object"==typeof e){const t={};for(const[s,r]of Object.entries(e))t[s]=this.serializeBigIntsAndDates(r);return t}return e}deserializeBigIntsAndDates(e,t=null){const s=t?t.safeStringEnabled:this.#a.safeStringEnabled,r=t?t.safeDateEnabled:this.#a.safeDateEnabled,a=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/;if("string"==typeof e){if(s&&e.startsWith("S:"))return e.substring(2);if(r&&e.startsWith("D:")){const t=e.substring(2),s=new Date(t);if(!isNaN(s.getTime()))return s}if(/^-?\d+n$/.test(e))return BigInt(e.slice(0,-1));if(!r&&a.test(e)){const t=new Date(e);if(!isNaN(t.getTime()))return t}}return Array.isArray(e)?e.map(e=>this.deserializeBigIntsAndDates(e,t)):e&&"object"==typeof e?Object.fromEntries(Object.entries(e).map(([e,s])=>[e,this.deserializeBigIntsAndDates(s,t)])):e}}export default t;module.exports.RpcClient=t;