rpc-express-toolkit 4.2.0 → 4.3.2
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/LICENSE +0 -0
- package/README.md +135 -9
- package/examples/example.js +0 -0
- package/examples/introspection/client.js +68 -0
- package/examples/introspection/server.js +89 -0
- package/examples/safe-preset-example.js +0 -0
- package/examples/server-client-example.js +0 -0
- package/examples/ssl-development-example.js +0 -0
- package/package.json +124 -126
- package/src/batch.js +95 -56
- package/src/index.d.ts +33 -63
- package/src/index.js +357 -46
- package/src/logger.js +17 -5
- package/src/middleware.js +0 -0
- package/src/protocol.js +97 -0
- package/src/safe.d.ts +0 -0
- package/src/safe.js +0 -0
- package/src/validation.js +1 -0
- package/src/clients/rpc-client.js +0 -403
- package/src/clients/rpc-client.min.js +0 -1
- package/src/clients/rpc-client.min.mjs +0 -1
- package/src/clients/rpc-client.mjs +0 -393
package/src/protocol.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
|
2
|
+
|
|
3
|
+
function isObject(value) {
|
|
4
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function isValidId(id) {
|
|
8
|
+
return id === null || typeof id === 'string' || typeof id === 'number';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function isValidParams(params) {
|
|
12
|
+
return Array.isArray(params) || isObject(params);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function validationError(message, id = null) {
|
|
16
|
+
return {
|
|
17
|
+
valid: false,
|
|
18
|
+
responseId: id,
|
|
19
|
+
error: {
|
|
20
|
+
code: -32600,
|
|
21
|
+
message,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function validateEnvelope(request) {
|
|
27
|
+
if (!isObject(request)) {
|
|
28
|
+
return validationError('Invalid Request: request must be an object');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const hasId = hasOwn(request, 'id');
|
|
32
|
+
const responseId = hasId && isValidId(request.id) ? request.id : null;
|
|
33
|
+
|
|
34
|
+
if (request.jsonrpc !== '2.0') {
|
|
35
|
+
return validationError(
|
|
36
|
+
"Invalid Request: 'jsonrpc' must be '2.0'.",
|
|
37
|
+
responseId
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (typeof request.method !== 'string') {
|
|
42
|
+
return validationError(
|
|
43
|
+
"Invalid Request: 'method' must be a string.",
|
|
44
|
+
responseId
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (request.method.length === 0) {
|
|
49
|
+
return validationError(
|
|
50
|
+
"Invalid Request: 'method' must not be empty.",
|
|
51
|
+
responseId
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (hasOwn(request, 'params') && !isValidParams(request.params)) {
|
|
56
|
+
return validationError(
|
|
57
|
+
"Invalid Request: 'params' must be an object or array.",
|
|
58
|
+
responseId
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (hasId && !isValidId(request.id)) {
|
|
63
|
+
return validationError(
|
|
64
|
+
"Invalid Request: 'id' must be a string, number, or null.",
|
|
65
|
+
null
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
valid: true,
|
|
71
|
+
hasId,
|
|
72
|
+
isNotification: !hasId,
|
|
73
|
+
responseId,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function addBatchIndex(error, batchIndex) {
|
|
78
|
+
const data =
|
|
79
|
+
error.data !== undefined && isObject(error.data) ? error.data : {};
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
...error,
|
|
83
|
+
data: {
|
|
84
|
+
...data,
|
|
85
|
+
...(error.data !== undefined && !isObject(error.data)
|
|
86
|
+
? { value: error.data }
|
|
87
|
+
: {}),
|
|
88
|
+
batchIndex,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = {
|
|
94
|
+
addBatchIndex,
|
|
95
|
+
hasOwn,
|
|
96
|
+
validateEnvelope,
|
|
97
|
+
};
|
package/src/safe.d.ts
CHANGED
|
File without changes
|
package/src/safe.js
CHANGED
|
File without changes
|
package/src/validation.js
CHANGED
|
@@ -12,6 +12,7 @@ class SchemaValidator {
|
|
|
12
12
|
removeAdditional: options.removeAdditional || false,
|
|
13
13
|
useDefaults: options.useDefaults !== false,
|
|
14
14
|
coerceTypes: options.coerceTypes || false,
|
|
15
|
+
strict: false, // Allow unknown keywords like exposeSchema in method options
|
|
15
16
|
...options.ajvOptions,
|
|
16
17
|
});
|
|
17
18
|
|
|
@@ -1,403 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JSON-RPC 2.0 Client for browser and Node.js environments.
|
|
3
|
-
* Handles BigInt and Date serialization/deserialization automatically
|
|
4
|
-
* using explicit serializers (no global prototype extensions).
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
// Select fetch implementation (Node 18+ or browser)
|
|
8
|
-
let fetchFn;
|
|
9
|
-
if (typeof globalThis !== 'undefined' && globalThis.fetch) {
|
|
10
|
-
fetchFn = globalThis.fetch.bind(globalThis);
|
|
11
|
-
} else {
|
|
12
|
-
throw new Error(
|
|
13
|
-
'globalThis.fetch is not available. Node.js 18+ is required.'
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
class RpcClient {
|
|
18
|
-
#endpoint;
|
|
19
|
-
|
|
20
|
-
#defaultHeaders;
|
|
21
|
-
|
|
22
|
-
#requestId;
|
|
23
|
-
|
|
24
|
-
#fetchOptions;
|
|
25
|
-
|
|
26
|
-
#options;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* @param {string} endpoint The JSON-RPC endpoint URL.
|
|
30
|
-
* @param {Object} [defaultHeaders={}] Optional default headers to include in requests.
|
|
31
|
-
* @param {Object} [options={}] Optional configuration options.
|
|
32
|
-
* @param {boolean} [options.rejectUnauthorized=true] Whether to reject unauthorized SSL certificates. Set to false for development with self-signed certificates.
|
|
33
|
-
* @param {boolean} [options.safeEnabled=false] Whether to use safe serialization for both strings and dates to avoid confusion with BigInt/ISO values. Default false for JSON-RPC 2.0 compliance.
|
|
34
|
-
* @param {boolean} [options.warnOnUnsafe=true] Whether to show educational warnings when BigInt/Date objects are serialized without safe mode. Default true.
|
|
35
|
-
*/
|
|
36
|
-
constructor(endpoint, defaultHeaders = {}, options = {}) {
|
|
37
|
-
this.#endpoint = endpoint;
|
|
38
|
-
this.#defaultHeaders = {
|
|
39
|
-
'Content-Type': 'application/json', // Default header
|
|
40
|
-
...defaultHeaders, // Merge with user-provided defaults
|
|
41
|
-
};
|
|
42
|
-
// Initialize request ID counter with timestamp + random component for uniqueness
|
|
43
|
-
this.#requestId = Date.now() * 1000 + Math.floor(Math.random() * 1000);
|
|
44
|
-
|
|
45
|
-
// Store options
|
|
46
|
-
this.#options = {
|
|
47
|
-
safeEnabled: options.safeEnabled === true, // Default false
|
|
48
|
-
warnOnUnsafe: options.warnOnUnsafe !== false, // Default true
|
|
49
|
-
...options,
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
// Store fetch options for Node.js environments
|
|
53
|
-
this.#fetchOptions = {};
|
|
54
|
-
// SSL validation: advanced options (agent/ca) have been removed for simplicity and compatibility.
|
|
55
|
-
// To bypass self-signed certificates in development, set:
|
|
56
|
-
// process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
57
|
-
// See README for details and best practices.
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Generate a unique request ID
|
|
62
|
-
* @returns {number} Unique request ID
|
|
63
|
-
*/
|
|
64
|
-
#generateId() {
|
|
65
|
-
return ++this.#requestId;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Make a JSON-RPC call to the server.
|
|
70
|
-
* @template T
|
|
71
|
-
* @param {string} method The RPC method name.
|
|
72
|
-
* @param {any} params The parameters to pass to the RPC method.
|
|
73
|
-
* @param {string|number|null} [id] The request ID (auto-generated if not provided).
|
|
74
|
-
* @param {Object} [overrideHeaders={}] Optional headers to override defaults for this request.
|
|
75
|
-
* @returns {Promise<T>} The result of the RPC call.
|
|
76
|
-
*/
|
|
77
|
-
async call(method, params, id = undefined, overrideHeaders = {}) {
|
|
78
|
-
// Auto-generate ID if not provided (null means notification)
|
|
79
|
-
const requestId = id === undefined ? this.#generateId() : id;
|
|
80
|
-
|
|
81
|
-
// Build the payload according to the spec: params omitted if undefined/null
|
|
82
|
-
const requestBody = {
|
|
83
|
-
jsonrpc: '2.0',
|
|
84
|
-
method,
|
|
85
|
-
id: requestId,
|
|
86
|
-
};
|
|
87
|
-
if (params !== undefined && params !== null) {
|
|
88
|
-
// Serialize parameters to handle BigInt and safe strings
|
|
89
|
-
requestBody.params = this.serializeBigIntsAndDates(params);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
const response = await fetchFn(this.#endpoint, {
|
|
94
|
-
method: 'POST',
|
|
95
|
-
headers: {
|
|
96
|
-
...this.#defaultHeaders,
|
|
97
|
-
// Add safe options header so server knows what client expects
|
|
98
|
-
'X-RPC-Safe-Enabled': this.#options.safeEnabled ? 'true' : 'false',
|
|
99
|
-
...overrideHeaders,
|
|
100
|
-
},
|
|
101
|
-
// Params are pre-serialized (BigInt/Date) via serializeBigIntsAndDates
|
|
102
|
-
body: JSON.stringify(requestBody),
|
|
103
|
-
...this.#fetchOptions, // Include any additional fetch options (e.g., SSL settings)
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
if (!response.ok) {
|
|
107
|
-
throw new Error(
|
|
108
|
-
`HTTP Error: ${response.status} ${response.statusText}`
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const responseBody = await response.json();
|
|
113
|
-
|
|
114
|
-
if (responseBody.error) {
|
|
115
|
-
throw responseBody.error;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Check server's safe options from response headers - strict compatibility checking
|
|
119
|
-
const serverSafeHeader = response.headers.get('X-RPC-Safe-Enabled');
|
|
120
|
-
|
|
121
|
-
// Errore rigoroso: se client ha safe enabled ma server non risponde con header
|
|
122
|
-
if (this.#options.safeEnabled && serverSafeHeader === null) {
|
|
123
|
-
throw new Error(
|
|
124
|
-
'RPC Compatibility Error: Client has safe serialization enabled but server did not respond with compatibility header (X-RPC-Safe-Enabled). ' +
|
|
125
|
-
'This may indicate a version mismatch or non-toolkit server. ' +
|
|
126
|
-
'Solutions: (1) Update server to rpc-express-toolkit v4+, (2) Disable client safeEnabled option, or (3) Use a compatible JSON-RPC server.'
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Warning: se client ha safe disabled ma server offre compatibilità
|
|
131
|
-
if (
|
|
132
|
-
!this.#options.safeEnabled &&
|
|
133
|
-
serverSafeHeader === 'true' &&
|
|
134
|
-
this.#options.warnOnUnsafe
|
|
135
|
-
) {
|
|
136
|
-
console.warn(
|
|
137
|
-
'⚠️ RPC Compatibility Notice: Server supports safe serialization but client has safeEnabled=false. ' +
|
|
138
|
-
'Consider enabling safeEnabled option for better BigInt/Date handling and forward compatibility.'
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const serverSafeEnabled = serverSafeHeader === 'true';
|
|
143
|
-
|
|
144
|
-
// Create deserialization options based on server's configuration
|
|
145
|
-
const deserializationOptions = {
|
|
146
|
-
safeEnabled: serverSafeEnabled,
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
// Convert back BigInts and Dates in the result using server's options
|
|
150
|
-
return this.deserializeBigIntsAndDates(
|
|
151
|
-
responseBody.result,
|
|
152
|
-
deserializationOptions
|
|
153
|
-
);
|
|
154
|
-
} catch (error) {
|
|
155
|
-
console.error('RPC call failed:', error);
|
|
156
|
-
throw error;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Send a notification (no response expected).
|
|
162
|
-
* @param {string} method The RPC method name.
|
|
163
|
-
* @param {any} params The parameters to pass to the RPC method.
|
|
164
|
-
* @param {Object} [overrideHeaders={}] Optional headers to override defaults for this request.
|
|
165
|
-
* @returns {Promise<void>} Promise that resolves when notification is sent.
|
|
166
|
-
*/
|
|
167
|
-
async notify(method, params = {}, overrideHeaders = {}) {
|
|
168
|
-
await this.call(method, params, null, overrideHeaders);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Make multiple JSON-RPC calls in a single batch request.
|
|
173
|
-
* @template T
|
|
174
|
-
* @param {Array<{method: string, params?: any, id?: string|number}>} requests Array of request objects.
|
|
175
|
-
* @param {Object} [overrideHeaders={}] Optional headers to override defaults for this request.
|
|
176
|
-
* @returns {Promise<Array<T>>} Array of results in the same order as requests.
|
|
177
|
-
*/
|
|
178
|
-
async batch(requests, overrideHeaders = {}) {
|
|
179
|
-
const batchRequests = requests.map((req) => {
|
|
180
|
-
const obj = {
|
|
181
|
-
jsonrpc: '2.0',
|
|
182
|
-
method: req.method,
|
|
183
|
-
id: req.id !== undefined ? req.id : this.#generateId(),
|
|
184
|
-
};
|
|
185
|
-
if (req.params !== undefined && req.params !== null) {
|
|
186
|
-
// Serialize parameters to handle BigInt and safe strings
|
|
187
|
-
obj.params = this.serializeBigIntsAndDates(req.params);
|
|
188
|
-
}
|
|
189
|
-
return obj;
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
const response = await fetchFn(this.#endpoint, {
|
|
194
|
-
method: 'POST',
|
|
195
|
-
headers: {
|
|
196
|
-
...this.#defaultHeaders,
|
|
197
|
-
// Add safe options header so server knows what client expects
|
|
198
|
-
'X-RPC-Safe-Enabled': this.#options.safeEnabled ? 'true' : 'false',
|
|
199
|
-
...overrideHeaders,
|
|
200
|
-
},
|
|
201
|
-
// Params are pre-serialized per item
|
|
202
|
-
body: JSON.stringify(batchRequests),
|
|
203
|
-
...this.#fetchOptions, // Include any additional fetch options (e.g., SSL settings)
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
if (!response.ok) {
|
|
207
|
-
throw new Error(
|
|
208
|
-
`HTTP Error: ${response.status} ${response.statusText}`
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const responseBody = await response.json();
|
|
213
|
-
|
|
214
|
-
// Check server's safe options from response headers - strict compatibility checking
|
|
215
|
-
const serverSafeHeader = response.headers.get('X-RPC-Safe-Enabled');
|
|
216
|
-
|
|
217
|
-
// Errore rigoroso: se client ha safe enabled ma server non risponde con header
|
|
218
|
-
if (this.#options.safeEnabled && serverSafeHeader === null) {
|
|
219
|
-
throw new Error(
|
|
220
|
-
'RPC Compatibility Error: Client has safe serialization enabled but server did not respond with compatibility header (X-RPC-Safe-Enabled). ' +
|
|
221
|
-
'This may indicate a version mismatch or non-toolkit server. ' +
|
|
222
|
-
'Solutions: (1) Update server to rpc-express-toolkit v4+, (2) Disable client safeEnabled option, or (3) Use a compatible JSON-RPC server.'
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Warning: se client ha safe disabled ma server offre compatibilità
|
|
227
|
-
if (
|
|
228
|
-
!this.#options.safeEnabled &&
|
|
229
|
-
serverSafeHeader === 'true' &&
|
|
230
|
-
this.#options.warnOnUnsafe
|
|
231
|
-
) {
|
|
232
|
-
console.warn(
|
|
233
|
-
'⚠️ RPC Compatibility Notice: Server supports safe serialization but client has safeEnabled=false. ' +
|
|
234
|
-
'Consider enabling safeEnabled option for better BigInt/Date handling and forward compatibility.'
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const serverSafeEnabled = serverSafeHeader === 'true';
|
|
239
|
-
|
|
240
|
-
// Create deserialization options based on server's configuration
|
|
241
|
-
const deserializationOptions = {
|
|
242
|
-
safeEnabled: serverSafeEnabled,
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
// Handle batch response
|
|
246
|
-
if (Array.isArray(responseBody)) {
|
|
247
|
-
return responseBody.map((res) => {
|
|
248
|
-
if (res.error) {
|
|
249
|
-
throw res.error;
|
|
250
|
-
}
|
|
251
|
-
return this.deserializeBigIntsAndDates(
|
|
252
|
-
res.result,
|
|
253
|
-
deserializationOptions
|
|
254
|
-
);
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
// Single response in batch
|
|
258
|
-
if (responseBody.error) {
|
|
259
|
-
throw responseBody.error;
|
|
260
|
-
}
|
|
261
|
-
return [
|
|
262
|
-
this.deserializeBigIntsAndDates(
|
|
263
|
-
responseBody.result,
|
|
264
|
-
deserializationOptions
|
|
265
|
-
),
|
|
266
|
-
];
|
|
267
|
-
} catch (error) {
|
|
268
|
-
console.error('Batch RPC call failed:', error);
|
|
269
|
-
throw error;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Recursively convert BigInt values to strings and Date objects to ISO strings
|
|
275
|
-
* so they can be JSON-serialized.
|
|
276
|
-
* @param {any} value
|
|
277
|
-
* @returns {any}
|
|
278
|
-
*/
|
|
279
|
-
serializeBigIntsAndDates(value) {
|
|
280
|
-
if (typeof value === 'bigint') {
|
|
281
|
-
// Convert BigInt to string with 'n' suffix for proper deserialization
|
|
282
|
-
return `${value.toString()}n`;
|
|
283
|
-
}
|
|
284
|
-
if (value instanceof Date) {
|
|
285
|
-
// Convert Date to ISO string with D: prefix if safeEnabled
|
|
286
|
-
const isoString = value.toISOString();
|
|
287
|
-
if (this.#options.safeEnabled) {
|
|
288
|
-
return `D:${isoString}`;
|
|
289
|
-
}
|
|
290
|
-
// Show educational warning if enabled
|
|
291
|
-
if (this.#options.warnOnUnsafe) {
|
|
292
|
-
console.warn(
|
|
293
|
-
'⚠️ Date serialization: Using plain ISO string format for JSON-RPC 2.0 compliance. Date objects will be deserialized as strings on the receiving end. Consider enabling safeEnabled or using string timestamps for better type safety.'
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
return isoString;
|
|
297
|
-
}
|
|
298
|
-
if (typeof value === 'string') {
|
|
299
|
-
// Add S: prefix if safeEnabled is true
|
|
300
|
-
if (this.#options.safeEnabled) {
|
|
301
|
-
return `S:${value}`;
|
|
302
|
-
}
|
|
303
|
-
// Show educational warning if enabled and string could be confused with BigInt
|
|
304
|
-
if (this.#options.warnOnUnsafe && /^-?\d+n?$/.test(value)) {
|
|
305
|
-
console.warn(
|
|
306
|
-
`⚠️ String serialization: String "${value}" could be confused with BigInt. Consider enabling safeEnabled for disambiguation or use explicit typing.`
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
|
-
return value;
|
|
310
|
-
}
|
|
311
|
-
if (Array.isArray(value)) {
|
|
312
|
-
// Recurse into arrays
|
|
313
|
-
return value.map((v) => this.serializeBigIntsAndDates(v));
|
|
314
|
-
}
|
|
315
|
-
if (value && typeof value === 'object') {
|
|
316
|
-
// Recurse into plain objects
|
|
317
|
-
const result = {};
|
|
318
|
-
for (const [key, val] of Object.entries(value)) {
|
|
319
|
-
result[key] = this.serializeBigIntsAndDates(val);
|
|
320
|
-
}
|
|
321
|
-
return result;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// If it's neither an array, an object, a Date, nor a bigint, return as-is
|
|
325
|
-
return value;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Recursively convert stringified BigInt values back to BigInt
|
|
330
|
-
* and ISO 8601 date strings back to Date objects.
|
|
331
|
-
*
|
|
332
|
-
* @param {any} value The value to deserialize.
|
|
333
|
-
* @param {Object} [options] Custom deserialization options (uses client options if not provided).
|
|
334
|
-
* @param {boolean} [options.safeEnabled] Whether to expect safe prefixes for strings and dates.
|
|
335
|
-
* @returns {any}
|
|
336
|
-
*/
|
|
337
|
-
deserializeBigIntsAndDates(value, options = null) {
|
|
338
|
-
// Use provided options or fall back to client options
|
|
339
|
-
const safeEnabled = options
|
|
340
|
-
? options.safeEnabled
|
|
341
|
-
: this.#options.safeEnabled;
|
|
342
|
-
|
|
343
|
-
// More comprehensive ISO date regex that handles:
|
|
344
|
-
// - UTC: 2023-01-01T12:00:00.000Z
|
|
345
|
-
// - With timezone: 2023-01-01T12:00:00.000+01:00
|
|
346
|
-
// - Without timezone: 2023-01-01T12:00:00.000 (treated as local)
|
|
347
|
-
const ISO_DATE_REGEX =
|
|
348
|
-
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/;
|
|
349
|
-
|
|
350
|
-
// 1. Check if it's a string that might be a BigInt, Date, or safe string
|
|
351
|
-
if (typeof value === 'string') {
|
|
352
|
-
// Safe string check: if safeEnabled and starts with S:
|
|
353
|
-
if (safeEnabled && value.startsWith('S:')) {
|
|
354
|
-
return value.substring(2); // Remove 'S:' prefix
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Safe date check: if safeEnabled and starts with D:
|
|
358
|
-
if (safeEnabled && value.startsWith('D:')) {
|
|
359
|
-
const isoString = value.substring(2); // Remove 'D:' prefix
|
|
360
|
-
const date = new Date(isoString);
|
|
361
|
-
// Double-check that we got a valid date
|
|
362
|
-
if (!Number.isNaN(date.getTime())) {
|
|
363
|
-
return date;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// BigInt check: only convert strings that explicitly end with "n"
|
|
368
|
-
// e.g., "42n", "-42n" but NOT "42", "0123456"
|
|
369
|
-
if (/^-?\d+n$/.test(value)) {
|
|
370
|
-
return BigInt(value.slice(0, -1)); // Remove 'n' and convert
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Date check: matches an ISO 8601 string (only if safeEnabled is false)
|
|
374
|
-
if (!safeEnabled && ISO_DATE_REGEX.test(value)) {
|
|
375
|
-
const date = new Date(value);
|
|
376
|
-
// Ensure it's valid
|
|
377
|
-
if (!Number.isNaN(date.getTime())) {
|
|
378
|
-
return date;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// 2. If it's an array, handle each element
|
|
384
|
-
if (Array.isArray(value)) {
|
|
385
|
-
return value.map((v) => this.deserializeBigIntsAndDates(v, options));
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// 3. If it's a plain object, recurse into each property
|
|
389
|
-
if (value && typeof value === 'object') {
|
|
390
|
-
return Object.fromEntries(
|
|
391
|
-
Object.entries(value).map(([key, val]) => [
|
|
392
|
-
key,
|
|
393
|
-
this.deserializeBigIntsAndDates(val, options),
|
|
394
|
-
])
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// 4. Fallback for primitives, etc.
|
|
399
|
-
return value;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
module.exports = RpcClient;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
let fetchFn;if("undefined"==typeof globalThis||!globalThis.fetch)throw new Error("globalThis.fetch is not available. Node.js 18+ is required.");fetchFn=globalThis.fetch.bind(globalThis);class RpcClient{#e;#t;#i;#s;#n;constructor(e,t={},i={}){this.#e=e,this.#t={"Content-Type":"application/json",...t},this.#i=1e3*Date.now()+Math.floor(1e3*Math.random()),this.#n={safeEnabled:!0===i.safeEnabled,warnOnUnsafe:!1!==i.warnOnUnsafe,...i},this.#s={}}#a(){return++this.#i}async call(e,t,i=void 0,s={}){const n={jsonrpc:"2.0",method:e,id:void 0===i?this.#a():i};null!=t&&(n.params=this.serializeBigIntsAndDates(t));try{const e=await fetchFn(this.#e,{method:"POST",headers:{...this.#t,"X-RPC-Safe-Enabled":this.#n.safeEnabled?"true":"false",...s},body:JSON.stringify(n),...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 i=e.headers.get("X-RPC-Safe-Enabled");if(this.#n.safeEnabled&&null===i)throw new Error("RPC Compatibility Error: Client has safe serialization enabled but server did not respond with compatibility header (X-RPC-Safe-Enabled). This may indicate a version mismatch or non-toolkit server. Solutions: (1) Update server to rpc-express-toolkit v4+, (2) Disable client safeEnabled option, or (3) Use a compatible JSON-RPC server.");!this.#n.safeEnabled&&"true"===i&&this.#n.warnOnUnsafe&&console.warn("⚠️ RPC Compatibility Notice: Server supports safe serialization but client has safeEnabled=false. Consider enabling safeEnabled option for better BigInt/Date handling and forward compatibility.");const a={safeEnabled:"true"===i};return this.deserializeBigIntsAndDates(t.result,a)}catch(e){throw console.error("RPC call failed:",e),e}}async notify(e,t={},i={}){await this.call(e,t,null,i)}async batch(e,t={}){const i=e.map(e=>{const t={jsonrpc:"2.0",method:e.method,id:void 0!==e.id?e.id:this.#a()};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-Safe-Enabled":this.#n.safeEnabled?"true":"false",...t},body:JSON.stringify(i),...this.#s});if(!e.ok)throw new Error(`HTTP Error: ${e.status} ${e.statusText}`);const s=await e.json(),n=e.headers.get("X-RPC-Safe-Enabled");if(this.#n.safeEnabled&&null===n)throw new Error("RPC Compatibility Error: Client has safe serialization enabled but server did not respond with compatibility header (X-RPC-Safe-Enabled). This may indicate a version mismatch or non-toolkit server. Solutions: (1) Update server to rpc-express-toolkit v4+, (2) Disable client safeEnabled option, or (3) Use a compatible JSON-RPC server.");!this.#n.safeEnabled&&"true"===n&&this.#n.warnOnUnsafe&&console.warn("⚠️ RPC Compatibility Notice: Server supports safe serialization but client has safeEnabled=false. Consider enabling safeEnabled option for better BigInt/Date handling and forward compatibility.");const a={safeEnabled:"true"===n};if(Array.isArray(s))return s.map(e=>{if(e.error)throw e.error;return this.deserializeBigIntsAndDates(e.result,a)});if(s.error)throw s.error;return[this.deserializeBigIntsAndDates(s.result,a)]}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.#n.safeEnabled?`D:${t}`:(this.#n.warnOnUnsafe&&console.warn("⚠️ Date serialization: Using plain ISO string format for JSON-RPC 2.0 compliance. Date objects will be deserialized as strings on the receiving end. Consider enabling safeEnabled or using string timestamps for better type safety."),t)}if("string"==typeof e)return this.#n.safeEnabled?`S:${e}`:(this.#n.warnOnUnsafe&&/^-?\d+n?$/.test(e)&&console.warn(`⚠️ String serialization: String "${e}" could be confused with BigInt. Consider enabling safeEnabled for disambiguation or use explicit typing.`),e);if(Array.isArray(e))return e.map(e=>this.serializeBigIntsAndDates(e));if(e&&"object"==typeof e){const t={};for(const[i,s]of Object.entries(e))t[i]=this.serializeBigIntsAndDates(s);return t}return e}deserializeBigIntsAndDates(e,t=null){const i=t?t.safeEnabled:this.#n.safeEnabled,s=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/;if("string"==typeof e){if(i&&e.startsWith("S:"))return e.substring(2);if(i&&e.startsWith("D:")){const t=e.substring(2),i=new Date(t);if(!Number.isNaN(i.getTime()))return i}if(/^-?\d+n$/.test(e))return BigInt(e.slice(0,-1));if(!i&&s.test(e)){const t=new Date(e);if(!Number.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,i])=>[e,this.deserializeBigIntsAndDates(i,t)])):e}}module.exports=RpcClient;
|
|
@@ -1 +0,0 @@
|
|
|
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;#i;#s;#n;constructor(e,t={},i={}){this.#e=e,this.#t={"Content-Type":"application/json",...t},this.#i=1e3*Date.now()+Math.floor(1e3*Math.random()),this.#n={safeEnabled:!0===i.safeEnabled,warnOnUnsafe:!1!==i.warnOnUnsafe,...i},this.#s={}}#r(){return++this.#i}async call(t,i,s=void 0,n={}){const r={jsonrpc:"2.0",method:t,id:void 0===s?this.#r():s};null!=i&&(r.params=this.serializeBigIntsAndDates(i));try{const t=await e(this.#e,{method:"POST",headers:{...this.#t,"X-RPC-Safe-Enabled":this.#n.safeEnabled?"true":"false",...n},body:JSON.stringify(r),...this.#s});if(!t.ok)throw new Error(`HTTP Error: ${t.status} ${t.statusText}`);const i=await t.json();if(i.error)throw i.error;const s=t.headers.get("X-RPC-Safe-Enabled");if(this.#n.safeEnabled&&null===s)throw new Error("RPC Compatibility Error: Client has safe serialization enabled but server did not respond with compatibility header (X-RPC-Safe-Enabled). This may indicate a version mismatch or non-toolkit server. Solutions: (1) Update server to rpc-express-toolkit v4+, (2) Disable client safeEnabled option, or (3) Use a compatible JSON-RPC server.");!this.#n.safeEnabled&&"true"===s&&this.#n.warnOnUnsafe&&console.warn("⚠️ RPC Compatibility Notice: Server supports safe serialization but client has safeEnabled=false. Consider enabling safeEnabled option for better BigInt/Date handling and forward compatibility.");const a={safeEnabled:"true"===s};return this.deserializeBigIntsAndDates(i.result,a)}catch(e){throw console.error("RPC call failed:",e),e}}async notify(e,t={},i={}){await this.call(e,t,null,i)}async batch(t,i={}){const s=t.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 t=await e(this.#e,{method:"POST",headers:{...this.#t,"X-RPC-Safe-Enabled":this.#n.safeEnabled?"true":"false",...i},body:JSON.stringify(s),...this.#s});if(!t.ok)throw new Error(`HTTP Error: ${t.status} ${t.statusText}`);const n=await t.json(),r=t.headers.get("X-RPC-Safe-Enabled");if(this.#n.safeEnabled&&null===r)throw new Error("RPC Compatibility Error: Client has safe serialization enabled but server did not respond with compatibility header (X-RPC-Safe-Enabled). This may indicate a version mismatch or non-toolkit server. Solutions: (1) Update server to rpc-express-toolkit v4+, (2) Disable client safeEnabled option, or (3) Use a compatible JSON-RPC server.");!this.#n.safeEnabled&&"true"===r&&this.#n.warnOnUnsafe&&console.warn("⚠️ RPC Compatibility Notice: Server supports safe serialization but client has safeEnabled=false. Consider enabling safeEnabled option for better BigInt/Date handling and forward compatibility.");const a={safeEnabled:"true"===r};if(Array.isArray(n))return n.map(e=>{if(e.error)throw e.error;return this.deserializeBigIntsAndDates(e.result,a)});if(n.error)throw n.error;return[this.deserializeBigIntsAndDates(n.result,a)]}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.#n.safeEnabled?`D:${t}`:(this.#n.warnOnUnsafe&&console.warn("⚠️ Date serialization: Using plain ISO string format for JSON-RPC 2.0 compliance. Date objects will be deserialized as strings on the receiving end. Consider enabling safeEnabled or using string timestamps for better type safety."),t)}if("string"==typeof e)return this.#n.safeEnabled?"S:"+e:(this.#n.warnOnUnsafe&&/^-?\d+n?$/.test(e)&&console.warn(`⚠️ String serialization: String "${e}" could be confused with BigInt. Consider enabling safeEnabled for disambiguation or use explicit typing.`),e);if(Array.isArray(e))return e.map(e=>this.serializeBigIntsAndDates(e));if(e&&"object"==typeof e){const t={};for(const[i,s]of Object.entries(e))t[i]=this.serializeBigIntsAndDates(s);return t}return e}deserializeBigIntsAndDates(e,t=null){const i=t?t.safeEnabled:this.#n.safeEnabled,s=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/;if("string"==typeof e){if(i&&e.startsWith("S:"))return e.substring(2);if(i&&e.startsWith("D:")){const t=e.substring(2),i=new Date(t);if(!isNaN(i.getTime()))return i}if(/^-?\d+n$/.test(e))return BigInt(e.slice(0,-1));if(!i&&s.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,i])=>[e,this.deserializeBigIntsAndDates(i,t)])):e}}export default t;export{t as RpcClient};
|