Skip to content

Commit 6ac87c6

Browse files
committed
feat(middleware): Enhance middleware with timeout and encapsulation features
- Import ClientRPCResponseResult and ClientRPCRequestParams from PK. - Implement timeoutMiddlewareServer and timeoutMiddlewareClient. - Integrate timeoutMiddleware into defaultMiddleware. - Fix Jest test issues. - Rename to RPCResponseResult and RPCRequestParams for clarity. - Perform lint fixes and Jest tests.
1 parent 46f9ecf commit 6ac87c6

File tree

5 files changed

+305
-95
lines changed

5 files changed

+305
-95
lines changed

src/middleware.ts

Lines changed: 104 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import type {
44
JSONRPCResponse,
55
JSONRPCResponseResult,
66
MiddlewareFactory,
7+
JSONValue,
8+
RPCRequestParams,
9+
RPCResponseResult,
710
} from './types';
11+
import type { ContextTimed } from '@matrixai/contexts';
812
import { TransformStream } from 'stream/web';
913
import { JSONParser } from '@streamparser/json';
1014
import * as utils from './utils';
@@ -75,6 +79,96 @@ function jsonMessageToBinaryStream(): TransformStream<
7579
});
7680
}
7781

82+
function timeoutMiddlewareServer(
83+
ctx: ContextTimed,
84+
_cancel: (reason?: any) => void,
85+
_meta: Record<string, JSONValue> | undefined,
86+
) {
87+
const currentTimeout = ctx.timer.delay;
88+
// Flags for tracking if the first message has been processed
89+
let forwardFirst = true;
90+
let reverseFirst = true;
91+
return {
92+
forward: new TransformStream<
93+
JSONRPCRequest<RPCRequestParams>,
94+
JSONRPCRequest<RPCRequestParams>
95+
>({
96+
transform: (chunk, controller) => {
97+
controller.enqueue(chunk);
98+
if (forwardFirst) {
99+
forwardFirst = false;
100+
const clientTimeout = chunk.metadata?.timeout;
101+
102+
if (clientTimeout == null) return;
103+
if (clientTimeout < currentTimeout) ctx.timer.reset(clientTimeout);
104+
}
105+
},
106+
}),
107+
reverse: new TransformStream<
108+
JSONRPCResponse<RPCResponseResult>,
109+
JSONRPCResponse<RPCResponseResult>
110+
>({
111+
transform: (chunk, controller) => {
112+
if (reverseFirst) {
113+
reverseFirst = false;
114+
if ('result' in chunk) {
115+
if (chunk.metadata == null) chunk.metadata = {};
116+
chunk.metadata.timeout = currentTimeout;
117+
}
118+
}
119+
controller.enqueue(chunk);
120+
},
121+
}),
122+
};
123+
}
124+
125+
/**
126+
* This adds its own timeout to the forward metadata and updates it's timeout
127+
* based on the reverse metadata.
128+
* @param ctx
129+
* @param _cancel
130+
* @param _meta
131+
*/
132+
function timeoutMiddlewareClient(
133+
ctx: ContextTimed,
134+
_cancel: (reason?: any) => void,
135+
_meta: Record<string, JSONValue> | undefined,
136+
) {
137+
const currentTimeout = ctx.timer.delay;
138+
// Flags for tracking if the first message has been processed
139+
let forwardFirst = true;
140+
let reverseFirst = true;
141+
return {
142+
forward: new TransformStream<JSONRPCRequest, JSONRPCRequest>({
143+
transform: (chunk, controller) => {
144+
if (forwardFirst) {
145+
forwardFirst = false;
146+
if (chunk == null) chunk = { jsonrpc: '2.0', method: '' };
147+
if (chunk.metadata == null) chunk.metadata = {};
148+
(chunk.metadata as any).timeout = currentTimeout;
149+
}
150+
controller.enqueue(chunk);
151+
},
152+
}),
153+
reverse: new TransformStream<
154+
JSONRPCResponse<RPCResponseResult>,
155+
JSONRPCResponse<RPCResponseResult>
156+
>({
157+
transform: (chunk, controller) => {
158+
controller.enqueue(chunk);
159+
if (reverseFirst) {
160+
reverseFirst = false;
161+
if ('result' in chunk) {
162+
const clientTimeout = chunk.result?.metadata?.timeout;
163+
if (clientTimeout == null) return;
164+
if (clientTimeout < currentTimeout) ctx.timer.reset(clientTimeout);
165+
}
166+
}
167+
},
168+
}),
169+
};
170+
}
171+
78172
/**
79173
* This function is a factory for creating a pass-through streamPair. It is used
80174
* as the default middleware for the middleware wrappers.
@@ -116,12 +210,14 @@ function defaultServerMiddlewareWrapper(
116210
>();
117211

118212
const middleMiddleware = middlewareFactory(ctx, cancel, meta);
213+
const timeoutMiddleware = timeoutMiddlewareServer(ctx, cancel, meta);
119214

120-
const forwardReadable = inputTransformStream.readable.pipeThrough(
121-
middleMiddleware.forward,
122-
); // Usual middleware here
215+
const forwardReadable = inputTransformStream.readable
216+
.pipeThrough(timeoutMiddleware.forward) // Timeout middleware here
217+
.pipeThrough(middleMiddleware.forward); // Usual middleware here
123218
const reverseReadable = outputTransformStream.readable
124219
.pipeThrough(middleMiddleware.reverse) // Usual middleware here
220+
.pipeThrough(timeoutMiddleware.reverse) // Timeout middleware here
125221
.pipeThrough(jsonMessageToBinaryStream());
126222

127223
return {
@@ -172,13 +268,15 @@ const defaultClientMiddlewareWrapper = (
172268
JSONRPCRequest
173269
>();
174270

271+
const timeoutMiddleware = timeoutMiddlewareClient(ctx, cancel, meta);
175272
const middleMiddleware = middleware(ctx, cancel, meta);
176273
const forwardReadable = inputTransformStream.readable
274+
.pipeThrough(timeoutMiddleware.forward)
177275
.pipeThrough(middleMiddleware.forward) // Usual middleware here
178276
.pipeThrough(jsonMessageToBinaryStream());
179-
const reverseReadable = outputTransformStream.readable.pipeThrough(
180-
middleMiddleware.reverse,
181-
); // Usual middleware here
277+
const reverseReadable = outputTransformStream.readable
278+
.pipeThrough(middleMiddleware.reverse)
279+
.pipeThrough(timeoutMiddleware.reverse); // Usual middleware here
182280

183281
return {
184282
forward: {

src/types.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type JSONRPCRequestMessage<T extends JSONValue = JSONValue> = {
3838
* SHOULD NOT contain fractional parts [2]
3939
*/
4040
id: string | number | null;
41-
};
41+
} & RPCResponseResult;
4242

4343
/**
4444
* This is the JSON RPC notification object. this is used for a request that
@@ -60,7 +60,7 @@ type JSONRPCRequestNotification<T extends JSONValue = JSONValue> = {
6060
* This member MAY be omitted.
6161
*/
6262
params?: T;
63-
};
63+
} & RPCResponseResult;
6464

6565
/**
6666
* This is the JSON RPC response result object. It contains the response data for a
@@ -84,7 +84,7 @@ type JSONRPCResponseResult<T extends JSONValue = JSONValue> = {
8484
* it MUST be Null.
8585
*/
8686
id: string | number | null;
87-
};
87+
} & RPCResponseResult;
8888

8989
/**
9090
* This is the JSON RPC response Error object. It contains any errors that have
@@ -110,6 +110,28 @@ type JSONRPCResponseError = {
110110
id: string | number | null;
111111
};
112112

113+
type ObjectEmpty = NonNullable<unknown>;
114+
115+
// Prevent overwriting the metadata type with `Omit<>`
116+
type RPCRequestParams<T extends Record<string, JSONValue> = ObjectEmpty> = {
117+
metadata?: {
118+
[Key: string]: JSONValue;
119+
} & Partial<{
120+
authorization: string;
121+
timeout: number | null;
122+
}>;
123+
} & Omit<T, 'metadata'>;
124+
125+
// Prevent overwriting the metadata type with `Omit<>`
126+
type RPCResponseResult<T extends Record<string, JSONValue> = ObjectEmpty> = {
127+
metadata?: {
128+
[Key: string]: JSONValue;
129+
} & Partial<{
130+
authorization: string;
131+
timeout: number | null;
132+
}>;
133+
} & Omit<T, 'metadata'>;
134+
113135
/**
114136
* This is a JSON RPC error object, it encodes the error data for the JSONRPCResponseError object.
115137
*/
@@ -357,6 +379,8 @@ export type {
357379
JSONRPCRequestNotification,
358380
JSONRPCResponseResult,
359381
JSONRPCResponseError,
382+
RPCRequestParams,
383+
RPCResponseResult,
360384
JSONRPCError,
361385
JSONRPCRequest,
362386
JSONRPCResponse,

src/utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import type {
1212
JSONRPCResponseResult,
1313
JSONValue,
1414
PromiseDeconstructed,
15+
RPCRequestParams,
16+
RPCResponseResult,
1517
ToError,
1618
} from './types';
1719
import { TransformStream } from 'stream/web';
@@ -262,6 +264,14 @@ function fromError(error: any): JSONValue {
262264
return error;
263265
}
264266

267+
function isRPCRequestParams(data: JSONValue): data is RPCRequestParams {
268+
return typeof data === 'object' && !Array.isArray(data);
269+
}
270+
271+
function isRPCResponseResult(data: JSONValue): data is RPCResponseResult {
272+
return typeof data === 'object' && !Array.isArray(data);
273+
}
274+
265275
/**
266276
* Error constructors for non-Polykey rpcErrors
267277
* Allows these rpcErrors to be reconstructed from RPC metadata
@@ -545,6 +555,8 @@ export {
545555
parseJSONRPCResponseError,
546556
parseJSONRPCResponse,
547557
parseJSONRPCMessage,
558+
isRPCRequestParams,
559+
isRPCResponseResult,
548560
filterSensitive,
549561
fromError,
550562
toError,

0 commit comments

Comments
 (0)