rpc-express-toolkit 2.3.3 → 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.
- package/README.md +221 -9
- package/examples/example.js +17 -18
- package/examples/server-client-example.js +120 -114
- package/examples/ssl-development-example.js +100 -80
- package/package.json +5 -5
- package/src/batch.js +256 -237
- package/src/clients/rpc-client.js +125 -44
- package/src/clients/rpc-client.min.js +1 -1
- package/src/clients/rpc-client.min.mjs +1 -1
- package/src/clients/rpc-client.mjs +159 -96
- package/src/index.d.ts +220 -58
- package/src/index.js +319 -115
- package/src/logger.js +184 -153
- package/src/middleware.js +229 -224
- package/src/validation.js +220 -221
|
@@ -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() {
|
|
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(
|
|
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
|
-
|
|
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:
|
|
99
|
+
jsonrpc: '2.0',
|
|
84
100
|
method,
|
|
85
101
|
id: requestId,
|
|
86
102
|
};
|
|
87
103
|
if (params !== undefined && params !== null) {
|
|
88
|
-
|
|
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:
|
|
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(
|
|
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) {
|
|
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
|
-
//
|
|
113
|
-
|
|
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(
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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(
|
|
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 ===
|
|
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
|
|
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 =
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if
|
|
233
|
-
|
|
234
|
-
//
|
|
235
|
-
if (
|
|
236
|
-
return
|
|
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
|
-
|
|
239
|
-
if
|
|
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 ===
|
|
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(
|
|
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
|
|
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;
|