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/src/middleware.js CHANGED
@@ -1,224 +1,229 @@
1
- "use strict";
2
-
3
- /**
4
- * Middleware manager for JSON-RPC operations
5
- */
6
- class MiddlewareManager {
7
- constructor() {
8
- this.middlewares = {
9
- beforeCall: [],
10
- afterCall: [],
11
- onError: [],
12
- beforeValidation: [],
13
- afterValidation: []
14
- };
15
- }
16
-
17
- /**
18
- * Add middleware for a specific hook
19
- * @param {string} hook - The hook name (beforeCall, afterCall, onError, etc.)
20
- * @param {Function} middleware - The middleware function
21
- */
22
- use(hook, middleware) {
23
- if (!this.middlewares[hook]) {
24
- throw new Error(`Unknown middleware hook: ${hook}`);
25
- }
26
-
27
- if (typeof middleware !== 'function') {
28
- throw new Error('Middleware must be a function');
29
- }
30
-
31
- this.middlewares[hook].push(middleware);
32
- }
33
-
34
- /**
35
- * Execute middlewares for a specific hook
36
- * @param {string} hook
37
- * @param {Object} context
38
- * @returns {Promise<Object>}
39
- */
40
- async execute(hook, context) {
41
- const middlewares = this.middlewares[hook] || [];
42
- let result = context;
43
-
44
- for (const middleware of middlewares) {
45
- try {
46
- const middlewareResult = await middleware(result);
47
-
48
- // If middleware returns an object, merge it with context
49
- if (middlewareResult && typeof middlewareResult === 'object') {
50
- result = { ...result, ...middlewareResult };
51
- }
52
-
53
- // If middleware explicitly returns false, stop execution
54
- if (middlewareResult === false) {
55
- throw new Error('Middleware execution stopped');
56
- }
57
- } catch (error) {
58
- // For error hooks, continue even if middleware fails
59
- if (hook === 'onError') {
60
- console.error('Error middleware failed:', error);
61
- continue;
62
- }
63
- throw error;
64
- }
65
- }
66
-
67
- return result;
68
- }
69
-
70
- /**
71
- * Get all middlewares for a hook
72
- * @param {string} hook
73
- * @returns {Function[]}
74
- */
75
- getMiddlewares(hook) {
76
- return this.middlewares[hook] || [];
77
- }
78
-
79
- /**
80
- * Remove all middlewares for a hook
81
- * @param {string} hook
82
- */
83
- clear(hook) {
84
- if (hook) {
85
- this.middlewares[hook] = [];
86
- } else {
87
- // Clear all hooks
88
- Object.keys(this.middlewares).forEach(h => {
89
- this.middlewares[h] = [];
90
- });
91
- }
92
- }
93
-
94
- /**
95
- * Remove a specific middleware from a hook
96
- * @param {string} hook
97
- * @param {Function} middleware
98
- */
99
- remove(hook, middleware) {
100
- if (!this.middlewares[hook]) return;
101
-
102
- const index = this.middlewares[hook].indexOf(middleware);
103
- if (index > -1) {
104
- this.middlewares[hook].splice(index, 1);
105
- }
106
- }
107
- }
108
-
109
- /**
110
- * Built-in middleware functions
111
- */
112
- const builtInMiddlewares = {
113
- /**
114
- * Rate limiting middleware
115
- * @param {Object} options
116
- * @returns {Function}
117
- */
118
- rateLimit(options = {}) {
119
- const windowMs = options.windowMs || 60000; // 1 minute
120
- const max = options.max || 100; // 100 requests per window
121
- const message = options.message || 'Too many requests';
122
-
123
- const requests = new Map();
124
-
125
- return async (context) => {
126
- const { req } = context;
127
- const key = req.ip || req.connection?.remoteAddress || 'unknown';
128
- const now = Date.now();
129
-
130
- // Clean old entries
131
- if (requests.has(key)) {
132
- const userRequests = requests.get(key);
133
- const validRequests = userRequests.filter(time => now - time < windowMs);
134
- requests.set(key, validRequests);
135
- }
136
-
137
- const userRequests = requests.get(key) || [];
138
-
139
- if (userRequests.length >= max) {
140
- const error = new Error(message);
141
- error.code = -32000; // Custom JSON-RPC error code
142
- throw error;
143
- }
144
-
145
- userRequests.push(now);
146
- requests.set(key, userRequests);
147
-
148
- return context;
149
- };
150
- },
151
-
152
- /**
153
- * Authentication middleware
154
- * @param {Function} authFunction
155
- * @returns {Function}
156
- */
157
- auth(authFunction) {
158
- return async (context) => {
159
- const { req } = context;
160
- const isAuthenticated = await authFunction(req);
161
-
162
- if (!isAuthenticated) {
163
- const error = new Error('Authentication required');
164
- error.code = -32001;
165
- throw error;
166
- }
167
-
168
- return context;
169
- };
170
- },
171
-
172
- /**
173
- * CORS middleware
174
- * @param {Object} options
175
- * @returns {Function}
176
- */
177
- cors(options = {}) {
178
- const origin = options.origin || '*';
179
- const methods = options.methods || ['POST'];
180
- const headers = options.headers || ['Content-Type'];
181
-
182
- return async (context) => {
183
- const { res } = context;
184
-
185
- res.header('Access-Control-Allow-Origin', origin);
186
- res.header('Access-Control-Allow-Methods', methods.join(', '));
187
- res.header('Access-Control-Allow-Headers', headers.join(', '));
188
-
189
- return context;
190
- };
191
- },
192
-
193
- /**
194
- * Request timing middleware
195
- * @returns {Function}
196
- */
197
- timing() {
198
- return async (context) => {
199
- context.startTime = Date.now();
200
- return context;
201
- };
202
- },
203
-
204
- /**
205
- * Method whitelist middleware
206
- * @param {string[]} allowedMethods
207
- * @returns {Function}
208
- */
209
- methodWhitelist(allowedMethods) {
210
- return async (context) => {
211
- const { method } = context;
212
-
213
- if (!allowedMethods.includes(method)) {
214
- const error = new Error(`Method '${method}' is not allowed`);
215
- error.code = -32601;
216
- throw error;
217
- }
218
-
219
- return context;
220
- };
221
- }
222
- };
223
-
224
- module.exports = { MiddlewareManager, builtInMiddlewares };
1
+ /**
2
+ * @file Middleware Manager
3
+ * @description Handles middleware execution for JSON-RPC requests including built-in middleware
4
+ */
5
+
6
+ /**
7
+ * Middleware manager for handling RPC middleware
8
+ */
9
+ class MiddlewareManager {
10
+ constructor() {
11
+ this.middlewares = {
12
+ beforeCall: [],
13
+ afterCall: [],
14
+ onError: [],
15
+ beforeValidation: [],
16
+ afterValidation: [],
17
+ };
18
+ }
19
+
20
+ /**
21
+ * Add middleware for a specific hook
22
+ * @param {string} hook - The hook name (beforeCall, afterCall, onError, etc.)
23
+ * @param {Function} middleware - The middleware function
24
+ */
25
+ use(hook, middleware) {
26
+ if (!this.middlewares[hook]) {
27
+ throw new Error(`Unknown middleware hook: ${hook}`);
28
+ }
29
+
30
+ if (typeof middleware !== 'function') {
31
+ throw new Error('Middleware must be a function');
32
+ }
33
+
34
+ this.middlewares[hook].push(middleware);
35
+ }
36
+
37
+ /**
38
+ * Execute middlewares for a specific hook
39
+ * @param {string} hook
40
+ * @param {Object} context
41
+ * @returns {Promise<Object>}
42
+ */
43
+ async execute(hook, context) {
44
+ const middlewares = this.middlewares[hook] || [];
45
+ let result = context;
46
+
47
+ for (const middleware of middlewares) {
48
+ try {
49
+ const middlewareResult = await middleware(result);
50
+
51
+ // If middleware returns an object, merge it with context
52
+ if (middlewareResult && typeof middlewareResult === 'object') {
53
+ result = { ...result, ...middlewareResult };
54
+ }
55
+
56
+ // If middleware explicitly returns false, stop execution
57
+ if (middlewareResult === false) {
58
+ throw new Error('Middleware execution stopped');
59
+ }
60
+ } catch (error) {
61
+ // For error hooks, continue even if middleware fails
62
+ if (hook === 'onError') {
63
+ console.error('Error middleware failed:', error);
64
+ continue;
65
+ }
66
+ throw error;
67
+ }
68
+ }
69
+
70
+ return result;
71
+ }
72
+
73
+ /**
74
+ * Get all middlewares for a hook
75
+ * @param {string} hook
76
+ * @returns {Function[]}
77
+ */
78
+ getMiddlewares(hook) {
79
+ return this.middlewares[hook] || [];
80
+ }
81
+
82
+ /**
83
+ * Remove all middlewares for a hook
84
+ * @param {string} hook
85
+ */
86
+ clear(hook) {
87
+ if (hook) {
88
+ this.middlewares[hook] = [];
89
+ } else {
90
+ // Clear all hooks
91
+ Object.keys(this.middlewares).forEach((h) => {
92
+ this.middlewares[h] = [];
93
+ });
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Remove a specific middleware from a hook
99
+ * @param {string} hook
100
+ * @param {Function} middleware
101
+ */
102
+ remove(hook, middleware) {
103
+ if (!this.middlewares[hook]) return;
104
+
105
+ const index = this.middlewares[hook].indexOf(middleware);
106
+ if (index > -1) {
107
+ this.middlewares[hook].splice(index, 1);
108
+ }
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Built-in middleware functions
114
+ */
115
+ const builtInMiddlewares = {
116
+ /**
117
+ * Rate limiting middleware
118
+ * @param {Object} options
119
+ * @returns {Function}
120
+ */
121
+ rateLimit(options = {}) {
122
+ const windowMs = options.windowMs || 60000; // 1 minute
123
+ const max = options.max || 100; // 100 requests per window
124
+ const message = options.message || 'Too many requests';
125
+
126
+ const requests = new Map();
127
+
128
+ return async (context) => {
129
+ const { req } = context;
130
+ const key = req.ip || req.connection?.remoteAddress || 'unknown';
131
+ const now = Date.now();
132
+
133
+ // Clean old entries
134
+ if (requests.has(key)) {
135
+ const userRequests = requests.get(key);
136
+ const validRequests = userRequests.filter(
137
+ (time) => now - time < windowMs
138
+ );
139
+ requests.set(key, validRequests);
140
+ }
141
+
142
+ const userRequests = requests.get(key) || [];
143
+
144
+ if (userRequests.length >= max) {
145
+ const error = new Error(message);
146
+ error.code = -32000; // Custom JSON-RPC error code
147
+ throw error;
148
+ }
149
+
150
+ userRequests.push(now);
151
+ requests.set(key, userRequests);
152
+
153
+ return context;
154
+ };
155
+ },
156
+
157
+ /**
158
+ * Authentication middleware
159
+ * @param {Function} authFunction
160
+ * @returns {Function}
161
+ */
162
+ auth(authFunction) {
163
+ return async (context) => {
164
+ const { req } = context;
165
+ const isAuthenticated = await authFunction(req);
166
+
167
+ if (!isAuthenticated) {
168
+ const error = new Error('Authentication required');
169
+ error.code = -32001;
170
+ throw error;
171
+ }
172
+
173
+ return context;
174
+ };
175
+ },
176
+
177
+ /**
178
+ * CORS middleware
179
+ * @param {Object} options
180
+ * @returns {Function}
181
+ */
182
+ cors(options = {}) {
183
+ const origin = options.origin || '*';
184
+ const methods = options.methods || ['POST'];
185
+ const headers = options.headers || ['Content-Type'];
186
+
187
+ return async (context) => {
188
+ const { res } = context;
189
+
190
+ res.header('Access-Control-Allow-Origin', origin);
191
+ res.header('Access-Control-Allow-Methods', methods.join(', '));
192
+ res.header('Access-Control-Allow-Headers', headers.join(', '));
193
+
194
+ return context;
195
+ };
196
+ },
197
+
198
+ /**
199
+ * Request timing middleware
200
+ * @returns {Function}
201
+ */
202
+ timing() {
203
+ return async (context) => {
204
+ context.startTime = Date.now();
205
+ return context;
206
+ };
207
+ },
208
+
209
+ /**
210
+ * Method whitelist middleware
211
+ * @param {string[]} allowedMethods
212
+ * @returns {Function}
213
+ */
214
+ methodWhitelist(allowedMethods) {
215
+ return async (context) => {
216
+ const { method } = context;
217
+
218
+ if (!allowedMethods.includes(method)) {
219
+ const error = new Error(`Method '${method}' is not allowed`);
220
+ error.code = -32601;
221
+ throw error;
222
+ }
223
+
224
+ return context;
225
+ };
226
+ },
227
+ };
228
+
229
+ module.exports = { MiddlewareManager, builtInMiddlewares };