@@ -4,19 +4,35 @@ import { NoncePeriodOption } from '../shared/options.js';
4
4
import { isExpired , nowSec , parseSeconds } from '../utils/time.js' ;
5
5
import { createLogger } from '../utils/logger.js' ;
6
6
7
- const logger = createLogger ( 'RequestManager ' ) ;
7
+ const logger = createLogger ( 'NodeRequestManager ' ) ;
8
8
9
9
/**
10
- * Request manager ( of the protocol node) initialization options type
10
+ * Type for initialization options of the request manager in the protocol node.
11
11
*/
12
12
export type RequestManagerOptions = NoncePeriodOption ;
13
13
14
+ /**
15
+ * Type for custom event for request
16
+ */
14
17
export interface RequestEvent < CustomRequestQuery extends GenericQuery > {
15
18
topic : string ;
16
19
data : RequestData < CustomRequestQuery > ;
17
20
}
18
21
19
- export interface RequestManagerEvents < CustomRequestQuery extends GenericQuery > {
22
+ /**
23
+ * Type of request item in the cache
24
+ */
25
+ interface RequestCacheItem < CustomRequestQuery extends GenericQuery > {
26
+ topic : string ;
27
+ data : RequestData < CustomRequestQuery > ;
28
+ }
29
+
30
+ /**
31
+ * NodeRequestManager events interface
32
+ */
33
+ export interface NodeRequestManagerEvents <
34
+ CustomRequestQuery extends GenericQuery ,
35
+ > {
20
36
/**
21
37
* @example
22
38
*
@@ -27,70 +43,166 @@ export interface RequestManagerEvents<CustomRequestQuery extends GenericQuery> {
27
43
* ```
28
44
*/
29
45
request : CustomEvent < RequestEvent < CustomRequestQuery > > ;
46
+
47
+ /**
48
+ * @example
49
+ *
50
+ * ```js
51
+ * request.addEventListener('request', () => {
52
+ * // ... request is ready
53
+ * })
54
+ * ```
55
+ */
56
+ error : CustomEvent < Error > ;
30
57
}
31
58
32
- export class RequestManager < CustomRequestQuery extends GenericQuery > extends EventEmitter <
33
- RequestManagerEvents < CustomRequestQuery >
34
- > {
59
+ /**
60
+ * Class for managing requests in a node
61
+ *
62
+ * @export
63
+ * @class NodeRequestManager
64
+ * @extends {EventEmitter<NodeRequestManagerEvents<CustomRequestQuery>> }
65
+ * @template CustomRequestQuery
66
+ */
67
+ export class NodeRequestManager <
68
+ CustomRequestQuery extends GenericQuery = GenericQuery ,
69
+ > extends EventEmitter < NodeRequestManagerEvents < CustomRequestQuery > > {
70
+ /** The period of time the manager waits for messages with a higher nonce. */
35
71
private noncePeriod : number ;
36
- private cache : Map < string , RequestData < CustomRequestQuery > > ;
37
- private cacheTopic : Map < string , string > ;
72
+ /** In-memory cache for messages. */
73
+ private cache : Map < string , RequestCacheItem < CustomRequestQuery > > ;
38
74
75
+ /**
76
+ * Creates an instance of NodeRequestManager.
77
+ * @param {RequestManagerOptions } options
78
+ * @memberof NodeRequestManager
79
+ */
39
80
constructor ( options : RequestManagerOptions ) {
40
81
super ( ) ;
41
82
42
83
const { noncePeriod } = options ;
43
84
44
85
// @todo Validate RequestManagerOptions
45
86
46
- this . cache = new Map < string , RequestData < CustomRequestQuery > > ( ) ; // requestId => request
47
- this . cacheTopic = new Map < string , string > ( ) ; // requestId => topic
87
+ // requestId => RequestCacheItem
88
+ this . cache = new Map < string , RequestCacheItem < CustomRequestQuery > > ( ) ;
89
+
90
+ this . noncePeriod = Number ( parseSeconds ( noncePeriod ) ) ;
91
+ }
92
+
93
+ /**
94
+ * Sets a new value of the `noncePeriod`
95
+ *
96
+ * @param {(number | string) } noncePeriod
97
+ * @memberof NodeRequestManager
98
+ */
99
+ setNoncePeriod ( noncePeriod : number | string ) {
48
100
this . noncePeriod = Number ( parseSeconds ( noncePeriod ) ) ;
49
101
}
50
102
51
- add ( topic : string , data : string ) {
52
- const requestData = JSON . parse ( data ) as RequestData < CustomRequestQuery > ;
103
+ /**
104
+ * Clears the requests cache
105
+ *
106
+ * @memberof NodeRequestManager
107
+ */
108
+ clear ( ) {
109
+ this . cache . clear ( ) ;
110
+ }
53
111
54
- if ( isExpired ( requestData . expire ) ) {
55
- logger . trace ( `Request #${ requestData . id } is expired` ) ;
56
- return ;
112
+ /**
113
+ * Deletes expired requests from the cache
114
+ *
115
+ * @memberof NodeRequestManager
116
+ */
117
+ prune ( ) {
118
+ const now = Math . ceil ( Date . now ( ) / 1000 ) ;
119
+ for ( const [ id , record ] of this . cache . entries ( ) ) {
120
+ try {
121
+ if ( record . data . expire < now ) {
122
+ this . cache . delete ( id ) ;
123
+ }
124
+ } catch ( error ) {
125
+ logger . error ( 'Cache prune error' , error ) ;
126
+ }
57
127
}
128
+ }
58
129
59
- if ( BigInt ( nowSec ( ) + this . noncePeriod ) > BigInt ( requestData . expire ) ) {
60
- logger . trace ( `Request #${ requestData . id } will expire before it can bee processed` ) ;
61
- return ;
62
- }
130
+ /**
131
+ * Adds a request to cache
132
+ *
133
+ * @param {string } requestTopic
134
+ * @param {string } data
135
+ * @memberof NodeRequestManager
136
+ */
137
+ add ( requestTopic : string , data : string ) {
138
+ try {
139
+ const requestData = JSON . parse ( data ) as RequestData < CustomRequestQuery > ;
140
+
141
+ // TODO: Implement validation of `data` type and `requestTopic`
142
+
143
+ // Check if request is expired
144
+ if ( isExpired ( requestData . expire ) ) {
145
+ logger . trace ( `Request #${ requestData . id } is expired` ) ;
146
+ return ;
147
+ }
148
+
149
+ // Check if request will expire before it can be processed
150
+ if ( BigInt ( nowSec ( ) + this . noncePeriod ) > BigInt ( requestData . expire ) ) {
151
+ logger . trace (
152
+ `Request #${ requestData . id } will expire before it can bee processed` ,
153
+ ) ;
154
+ return ;
155
+ }
156
+
157
+ // If request is new, add to cache and set a timeout to dispatch event
158
+ if ( ! this . cache . has ( requestData . id ) ) {
159
+ // New request
160
+ this . cache . set ( requestData . id , {
161
+ data : requestData ,
162
+ topic : requestTopic ,
163
+ } ) ;
63
164
64
- if ( ! this . cache . has ( requestData . id ) ) {
65
- // New request
66
- this . cache . set ( requestData . id , requestData ) ;
67
- this . cacheTopic . set ( requestData . id , topic ) ;
68
- setTimeout ( ( ) => {
69
- try {
70
- this . dispatchEvent (
71
- new CustomEvent ( 'request' , {
72
- detail : {
73
- topic,
74
- data : requestData ,
75
- } ,
76
- } ) ,
77
- ) ;
78
-
79
- if ( ! this . cache . delete ( requestData . id ) || ! this . cacheTopic . delete ( requestData . id ) ) {
80
- throw new Error ( `Unable to remove request #${ requestData . id } from cache` ) ;
165
+ // Wait until the nonce period ends
166
+ setTimeout ( ( ) => {
167
+ try {
168
+ const cacheItem = this . cache . get ( requestData . id ) ;
169
+
170
+ if ( cacheItem ) {
171
+ this . dispatchEvent (
172
+ new CustomEvent ( 'request' , {
173
+ detail : {
174
+ topic : cacheItem . topic ,
175
+ data : cacheItem . data ,
176
+ } ,
177
+ } ) ,
178
+ ) ;
179
+
180
+ if ( ! this . cache . delete ( requestData . id ) ) {
181
+ throw new Error (
182
+ `Unable to remove request #${ requestData . id } from cache` ,
183
+ ) ;
184
+ }
185
+ }
186
+ } catch ( error ) {
187
+ logger . error ( error ) ;
81
188
}
82
- } catch ( error ) {
83
- logger . error ( error ) ;
189
+ } , this . noncePeriod * 1000 ) ;
190
+ } else {
191
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
192
+ const { topic, data } = this . cache . get ( requestData . id ) ! ;
193
+
194
+ // If request is known, only update if nonce is higher and the same topic
195
+ if ( requestTopic === topic && requestData . nonce > data . nonce ) {
196
+ this . cache . set ( requestData . id , { data : requestData , topic } ) ;
84
197
}
85
- } , this . noncePeriod * 1000 ) ;
86
- } else {
87
- // Known request
88
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
89
- const knownRequest = this . cache . get ( requestData . id ) ! ;
90
-
91
- if ( knownRequest . nonce < requestData . nonce ) {
92
- this . cache . set ( requestData . id , requestData ) ;
93
198
}
199
+ } catch ( error ) {
200
+ logger . error ( error ) ;
201
+ this . dispatchEvent (
202
+ new CustomEvent ( 'error' , {
203
+ detail : new Error ( 'Unable to add request to cache due to error' ) ,
204
+ } ) ,
205
+ ) ;
94
206
}
95
207
}
96
208
}
0 commit comments