@trpc/server 11.1.0 → 11.1.1
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/dist/adapters/fastify/fastifyTRPCPlugin.js +2 -2
- package/dist/adapters/fastify/fastifyTRPCPlugin.mjs +2 -2
- package/dist/adapters/ws.d.ts +1 -1
- package/dist/adapters/ws.d.ts.map +1 -1
- package/dist/adapters/ws.js +102 -84
- package/dist/adapters/ws.mjs +102 -84
- package/package.json +2 -2
- package/src/adapters/fastify/fastifyTRPCPlugin.ts +2 -2
- package/src/adapters/ws.ts +115 -91
|
@@ -42,8 +42,8 @@ function fastifyTRPCPlugin(fastify, opts, done) {
|
|
|
42
42
|
});
|
|
43
43
|
fastify.get(prefix ?? '/', {
|
|
44
44
|
websocket: true
|
|
45
|
-
},
|
|
46
|
-
|
|
45
|
+
}, (socket, req)=>{
|
|
46
|
+
onConnection(socket, req.raw);
|
|
47
47
|
if (trpcOptions?.keepAlive?.enabled) {
|
|
48
48
|
const { pingMs, pongWaitMs } = trpcOptions.keepAlive;
|
|
49
49
|
ws.handleKeepAlive(socket, pingMs, pongWaitMs);
|
|
@@ -40,8 +40,8 @@ function fastifyTRPCPlugin(fastify, opts, done) {
|
|
|
40
40
|
});
|
|
41
41
|
fastify.get(prefix ?? '/', {
|
|
42
42
|
websocket: true
|
|
43
|
-
},
|
|
44
|
-
|
|
43
|
+
}, (socket, req)=>{
|
|
44
|
+
onConnection(socket, req.raw);
|
|
45
45
|
if (trpcOptions?.keepAlive?.enabled) {
|
|
46
46
|
const { pingMs, pongWaitMs } = trpcOptions.keepAlive;
|
|
47
47
|
handleKeepAlive(socket, pingMs, pongWaitMs);
|
package/dist/adapters/ws.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ export type WSSHandlerOptions<TRouter extends AnyRouter> = WSConnectionHandlerOp
|
|
|
43
43
|
*/
|
|
44
44
|
dangerouslyDisablePong?: boolean;
|
|
45
45
|
};
|
|
46
|
-
export declare function getWSConnectionHandler<TRouter extends AnyRouter>(opts: WSSHandlerOptions<TRouter>): (client: ws.WebSocket, req: IncomingMessage) =>
|
|
46
|
+
export declare function getWSConnectionHandler<TRouter extends AnyRouter>(opts: WSSHandlerOptions<TRouter>): (client: ws.WebSocket, req: IncomingMessage) => void;
|
|
47
47
|
/**
|
|
48
48
|
* Handle WebSocket keep-alive messages
|
|
49
49
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../../src/adapters/ws.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,EACV,SAAS,EACT,qBAAqB,EACrB,kBAAkB,EACnB,MAAM,iBAAiB,CAAC;AASzB,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAa/D,OAAO,EAKL,KAAK,YAAY,EAClB,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"ws.d.ts","sourceRoot":"","sources":["../../src/adapters/ws.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAC5C,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,EACV,SAAS,EACT,qBAAqB,EACrB,kBAAkB,EACnB,MAAM,iBAAiB,CAAC;AASzB,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAa/D,OAAO,EAKL,KAAK,YAAY,EAClB,MAAM,gCAAgC,CAAC;AAMxC,OAAO,EAAa,KAAK,8BAA8B,EAAE,MAAM,aAAa,CAAC;AAQ7E;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG,8BAA8B,CACpE,eAAe,EACf,EAAE,CAAC,SAAS,CACb,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,kBAAkB,CAAC,OAAO,SAAS,SAAS,IAAI,CAC1D,IAAI,EAAE,yBAAyB,KAC5B,YAAY,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC;AAE/C,MAAM,MAAM,0BAA0B,CAAC,OAAO,SAAS,SAAS,IAC9D,kBAAkB,CAAC,OAAO,EAAE,eAAe,CAAC,GAC1C,qBAAqB,CACnB,kBAAkB,CAAC,OAAO,CAAC,EAC3B,kBAAkB,CAAC,OAAO,CAAC,CAC5B,CAAC;AAEN;;GAEG;AACH,MAAM,MAAM,iBAAiB,CAAC,OAAO,SAAS,SAAS,IACrD,0BAA0B,CAAC,OAAO,CAAC,GAAG;IACpC,GAAG,EAAE,EAAE,CAAC,eAAe,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE;QACV;;;WAGG;QACH,OAAO,EAAE,OAAO,CAAC;QACjB;;;WAGG;QACH,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB;;;WAGG;QACH,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,CAAC;AAEJ,wBAAgB,sBAAsB,CAAC,OAAO,SAAS,SAAS,EAC9D,IAAI,EAAE,iBAAiB,CAAC,OAAO,CAAC,IAKxB,QAAQ,EAAE,CAAC,SAAS,EAAE,KAAK,eAAe,UAyanD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,EAAE,CAAC,SAAS,EACpB,MAAM,SAAS,EACf,UAAU,SAAQ,QAiCnB;AAED,wBAAgB,eAAe,CAAC,OAAO,SAAS,SAAS,EACvD,IAAI,EAAE,iBAAiB,CAAC,OAAO,CAAC;;EAyBjC"}
|
package/dist/adapters/ws.js
CHANGED
|
@@ -84,11 +84,10 @@ function _ts_dispose_resources(env) {
|
|
|
84
84
|
* Importing ws causes a build error
|
|
85
85
|
* @see https://github.com/trpc/trpc/pull/5279
|
|
86
86
|
*/ const WEBSOCKET_OPEN = 1; /* ws.WebSocket.OPEN */
|
|
87
|
-
const unsetContextPromiseSymbol = Symbol('unsetContextPromise');
|
|
88
87
|
function getWSConnectionHandler(opts) {
|
|
89
88
|
const { createContext, router: router$1 } = opts;
|
|
90
89
|
const { transformer: transformer$1 } = router$1._def._config;
|
|
91
|
-
return
|
|
90
|
+
return (client, req)=>{
|
|
92
91
|
const clientSubscriptions = new Map();
|
|
93
92
|
const abortController = new AbortController();
|
|
94
93
|
if (opts.keepAlive?.enabled) {
|
|
@@ -98,23 +97,28 @@ function getWSConnectionHandler(opts) {
|
|
|
98
97
|
function respond(untransformedJSON) {
|
|
99
98
|
client.send(JSON.stringify(transformer.transformTRPCResponse(router$1._def._config, untransformedJSON)));
|
|
100
99
|
}
|
|
101
|
-
function createCtxPromise(getConnectionParams) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
100
|
+
async function createCtxPromise(getConnectionParams) {
|
|
101
|
+
try {
|
|
102
|
+
return await utils.run(async ()=>{
|
|
103
|
+
ctx = await createContext?.({
|
|
104
|
+
req,
|
|
105
|
+
res: client,
|
|
106
|
+
info: {
|
|
107
|
+
connectionParams: getConnectionParams(),
|
|
108
|
+
calls: [],
|
|
109
|
+
isBatchCall: false,
|
|
110
|
+
accept: null,
|
|
111
|
+
type: 'unknown',
|
|
112
|
+
signal: abortController.signal,
|
|
113
|
+
url: null
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
return {
|
|
117
|
+
ok: true,
|
|
118
|
+
value: ctx
|
|
119
|
+
};
|
|
115
120
|
});
|
|
116
|
-
|
|
117
|
-
}).catch((cause)=>{
|
|
121
|
+
} catch (cause) {
|
|
118
122
|
const error = TRPCError.getTRPCErrorFromUnknown(cause);
|
|
119
123
|
opts.onError?.({
|
|
120
124
|
error,
|
|
@@ -139,8 +143,11 @@ function getWSConnectionHandler(opts) {
|
|
|
139
143
|
(globalThis.setImmediate ?? globalThis.setTimeout)(()=>{
|
|
140
144
|
client.close();
|
|
141
145
|
});
|
|
142
|
-
|
|
143
|
-
|
|
146
|
+
return {
|
|
147
|
+
ok: false,
|
|
148
|
+
error
|
|
149
|
+
};
|
|
150
|
+
}
|
|
144
151
|
}
|
|
145
152
|
let ctx = undefined;
|
|
146
153
|
/**
|
|
@@ -148,14 +155,35 @@ function getWSConnectionHandler(opts) {
|
|
|
148
155
|
*
|
|
149
156
|
* - the context promise will be created immediately on connection if no connectionParams are expected
|
|
150
157
|
* - if connection params are expected, they will be created once received
|
|
151
|
-
*/ let ctxPromise = incomingMessageToRequest.createURL(req).searchParams.get('connectionParams') === '1' ?
|
|
152
|
-
|
|
158
|
+
*/ let ctxPromise = incomingMessageToRequest.createURL(req).searchParams.get('connectionParams') === '1' ? null : createCtxPromise(()=>null);
|
|
159
|
+
function handleRequest(msg) {
|
|
153
160
|
const { id, jsonrpc } = msg;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
code: '
|
|
161
|
+
if (id === null) {
|
|
162
|
+
const error = TRPCError.getTRPCErrorFromUnknown(new TRPCError.TRPCError({
|
|
163
|
+
code: 'PARSE_ERROR',
|
|
157
164
|
message: '`id` is required'
|
|
165
|
+
}));
|
|
166
|
+
opts.onError?.({
|
|
167
|
+
error,
|
|
168
|
+
path: undefined,
|
|
169
|
+
type: 'unknown',
|
|
170
|
+
ctx,
|
|
171
|
+
req,
|
|
172
|
+
input: undefined
|
|
158
173
|
});
|
|
174
|
+
respond({
|
|
175
|
+
id,
|
|
176
|
+
jsonrpc,
|
|
177
|
+
error: getErrorShape.getErrorShape({
|
|
178
|
+
config: router$1._def._config,
|
|
179
|
+
error,
|
|
180
|
+
type: 'unknown',
|
|
181
|
+
path: undefined,
|
|
182
|
+
input: undefined,
|
|
183
|
+
ctx
|
|
184
|
+
})
|
|
185
|
+
});
|
|
186
|
+
return;
|
|
159
187
|
}
|
|
160
188
|
if (msg.method === 'subscription.stop') {
|
|
161
189
|
clientSubscriptions.get(id)?.abort();
|
|
@@ -164,20 +192,24 @@ function getWSConnectionHandler(opts) {
|
|
|
164
192
|
const { path, lastEventId } = msg.params;
|
|
165
193
|
let { input } = msg.params;
|
|
166
194
|
const type = msg.method;
|
|
167
|
-
|
|
168
|
-
if (
|
|
169
|
-
|
|
170
|
-
input
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
195
|
+
if (lastEventId !== undefined) {
|
|
196
|
+
if (utils.isObject(input)) {
|
|
197
|
+
input = {
|
|
198
|
+
...input,
|
|
199
|
+
lastEventId: lastEventId
|
|
200
|
+
};
|
|
201
|
+
} else {
|
|
202
|
+
input ?? (input = {
|
|
203
|
+
lastEventId: lastEventId
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
utils.run(async ()=>{
|
|
208
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
209
|
+
const res = await ctxPromise; // asserts context has been set
|
|
210
|
+
if (!res.ok) {
|
|
211
|
+
throw res.error;
|
|
179
212
|
}
|
|
180
|
-
await ctxPromise; // asserts context has been set
|
|
181
213
|
const abortController = new AbortController();
|
|
182
214
|
const result = await router.callProcedure({
|
|
183
215
|
router: router$1,
|
|
@@ -345,7 +377,7 @@ function getWSConnectionHandler(opts) {
|
|
|
345
377
|
type: 'started'
|
|
346
378
|
}
|
|
347
379
|
});
|
|
348
|
-
}
|
|
380
|
+
}).catch((cause)=>{
|
|
349
381
|
// procedure threw an error
|
|
350
382
|
const error = TRPCError.getTRPCErrorFromUnknown(cause);
|
|
351
383
|
opts.onError?.({
|
|
@@ -368,9 +400,9 @@ function getWSConnectionHandler(opts) {
|
|
|
368
400
|
ctx
|
|
369
401
|
})
|
|
370
402
|
});
|
|
371
|
-
}
|
|
403
|
+
});
|
|
372
404
|
}
|
|
373
|
-
client.on('message',
|
|
405
|
+
client.on('message', (rawData)=>{
|
|
374
406
|
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
375
407
|
const msgStr = rawData.toString();
|
|
376
408
|
if (msgStr === 'PONG') {
|
|
@@ -382,7 +414,7 @@ function getWSConnectionHandler(opts) {
|
|
|
382
414
|
}
|
|
383
415
|
return;
|
|
384
416
|
}
|
|
385
|
-
if (ctxPromise
|
|
417
|
+
if (!ctxPromise) {
|
|
386
418
|
// If the ctxPromise wasn't created immediately, we're expecting the first message to be a TRPCConnectionParamsMessage
|
|
387
419
|
ctxPromise = createCtxPromise(()=>{
|
|
388
420
|
let msg;
|
|
@@ -403,30 +435,33 @@ function getWSConnectionHandler(opts) {
|
|
|
403
435
|
});
|
|
404
436
|
return;
|
|
405
437
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
msgJSON
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
438
|
+
const parsedMsgs = utils.run(()=>{
|
|
439
|
+
try {
|
|
440
|
+
const msgJSON = JSON.parse(msgStr);
|
|
441
|
+
const msgs = Array.isArray(msgJSON) ? msgJSON : [
|
|
442
|
+
msgJSON
|
|
443
|
+
];
|
|
444
|
+
return msgs.map((raw)=>parseTRPCMessage.parseTRPCMessage(raw, transformer$1));
|
|
445
|
+
} catch (cause) {
|
|
446
|
+
const error = new TRPCError.TRPCError({
|
|
447
|
+
code: 'PARSE_ERROR',
|
|
448
|
+
cause
|
|
449
|
+
});
|
|
450
|
+
respond({
|
|
451
|
+
id: null,
|
|
452
|
+
error: getErrorShape.getErrorShape({
|
|
453
|
+
config: router$1._def._config,
|
|
454
|
+
error,
|
|
455
|
+
type: 'unknown',
|
|
456
|
+
path: undefined,
|
|
457
|
+
input: undefined,
|
|
458
|
+
ctx
|
|
459
|
+
})
|
|
460
|
+
});
|
|
461
|
+
return [];
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
parsedMsgs.map(handleRequest);
|
|
430
465
|
});
|
|
431
466
|
// WebSocket errors should be handled, as otherwise unhandled exceptions will crash Node.js.
|
|
432
467
|
// This line was introduced after the following error brought down production systems:
|
|
@@ -449,9 +484,6 @@ function getWSConnectionHandler(opts) {
|
|
|
449
484
|
clientSubscriptions.clear();
|
|
450
485
|
abortController.abort();
|
|
451
486
|
});
|
|
452
|
-
if (ctxPromise !== unsetContextPromiseSymbol) {
|
|
453
|
-
await ctxPromise;
|
|
454
|
-
}
|
|
455
487
|
};
|
|
456
488
|
}
|
|
457
489
|
/**
|
|
@@ -488,21 +520,7 @@ function applyWSSHandler(opts) {
|
|
|
488
520
|
if (opts.prefix && !req.url?.startsWith(opts.prefix)) {
|
|
489
521
|
return;
|
|
490
522
|
}
|
|
491
|
-
onConnection(client, req)
|
|
492
|
-
opts.onError?.({
|
|
493
|
-
error: new TRPCError.TRPCError({
|
|
494
|
-
code: 'INTERNAL_SERVER_ERROR',
|
|
495
|
-
cause,
|
|
496
|
-
message: 'Failed to handle WebSocket connection'
|
|
497
|
-
}),
|
|
498
|
-
req: req,
|
|
499
|
-
path: undefined,
|
|
500
|
-
type: 'unknown',
|
|
501
|
-
ctx: undefined,
|
|
502
|
-
input: undefined
|
|
503
|
-
});
|
|
504
|
-
client.close();
|
|
505
|
-
});
|
|
523
|
+
onConnection(client, req);
|
|
506
524
|
});
|
|
507
525
|
return {
|
|
508
526
|
broadcastReconnectNotification: ()=>{
|
package/dist/adapters/ws.mjs
CHANGED
|
@@ -82,11 +82,10 @@ function _ts_dispose_resources(env) {
|
|
|
82
82
|
* Importing ws causes a build error
|
|
83
83
|
* @see https://github.com/trpc/trpc/pull/5279
|
|
84
84
|
*/ const WEBSOCKET_OPEN = 1; /* ws.WebSocket.OPEN */
|
|
85
|
-
const unsetContextPromiseSymbol = Symbol('unsetContextPromise');
|
|
86
85
|
function getWSConnectionHandler(opts) {
|
|
87
86
|
const { createContext, router } = opts;
|
|
88
87
|
const { transformer } = router._def._config;
|
|
89
|
-
return
|
|
88
|
+
return (client, req)=>{
|
|
90
89
|
const clientSubscriptions = new Map();
|
|
91
90
|
const abortController = new AbortController();
|
|
92
91
|
if (opts.keepAlive?.enabled) {
|
|
@@ -96,23 +95,28 @@ function getWSConnectionHandler(opts) {
|
|
|
96
95
|
function respond(untransformedJSON) {
|
|
97
96
|
client.send(JSON.stringify(transformTRPCResponse(router._def._config, untransformedJSON)));
|
|
98
97
|
}
|
|
99
|
-
function createCtxPromise(getConnectionParams) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
98
|
+
async function createCtxPromise(getConnectionParams) {
|
|
99
|
+
try {
|
|
100
|
+
return await run(async ()=>{
|
|
101
|
+
ctx = await createContext?.({
|
|
102
|
+
req,
|
|
103
|
+
res: client,
|
|
104
|
+
info: {
|
|
105
|
+
connectionParams: getConnectionParams(),
|
|
106
|
+
calls: [],
|
|
107
|
+
isBatchCall: false,
|
|
108
|
+
accept: null,
|
|
109
|
+
type: 'unknown',
|
|
110
|
+
signal: abortController.signal,
|
|
111
|
+
url: null
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
return {
|
|
115
|
+
ok: true,
|
|
116
|
+
value: ctx
|
|
117
|
+
};
|
|
113
118
|
});
|
|
114
|
-
|
|
115
|
-
}).catch((cause)=>{
|
|
119
|
+
} catch (cause) {
|
|
116
120
|
const error = getTRPCErrorFromUnknown(cause);
|
|
117
121
|
opts.onError?.({
|
|
118
122
|
error,
|
|
@@ -137,8 +141,11 @@ function getWSConnectionHandler(opts) {
|
|
|
137
141
|
(globalThis.setImmediate ?? globalThis.setTimeout)(()=>{
|
|
138
142
|
client.close();
|
|
139
143
|
});
|
|
140
|
-
|
|
141
|
-
|
|
144
|
+
return {
|
|
145
|
+
ok: false,
|
|
146
|
+
error
|
|
147
|
+
};
|
|
148
|
+
}
|
|
142
149
|
}
|
|
143
150
|
let ctx = undefined;
|
|
144
151
|
/**
|
|
@@ -146,14 +153,35 @@ function getWSConnectionHandler(opts) {
|
|
|
146
153
|
*
|
|
147
154
|
* - the context promise will be created immediately on connection if no connectionParams are expected
|
|
148
155
|
* - if connection params are expected, they will be created once received
|
|
149
|
-
*/ let ctxPromise = createURL(req).searchParams.get('connectionParams') === '1' ?
|
|
150
|
-
|
|
156
|
+
*/ let ctxPromise = createURL(req).searchParams.get('connectionParams') === '1' ? null : createCtxPromise(()=>null);
|
|
157
|
+
function handleRequest(msg) {
|
|
151
158
|
const { id, jsonrpc } = msg;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
code: '
|
|
159
|
+
if (id === null) {
|
|
160
|
+
const error = getTRPCErrorFromUnknown(new TRPCError({
|
|
161
|
+
code: 'PARSE_ERROR',
|
|
155
162
|
message: '`id` is required'
|
|
163
|
+
}));
|
|
164
|
+
opts.onError?.({
|
|
165
|
+
error,
|
|
166
|
+
path: undefined,
|
|
167
|
+
type: 'unknown',
|
|
168
|
+
ctx,
|
|
169
|
+
req,
|
|
170
|
+
input: undefined
|
|
156
171
|
});
|
|
172
|
+
respond({
|
|
173
|
+
id,
|
|
174
|
+
jsonrpc,
|
|
175
|
+
error: getErrorShape({
|
|
176
|
+
config: router._def._config,
|
|
177
|
+
error,
|
|
178
|
+
type: 'unknown',
|
|
179
|
+
path: undefined,
|
|
180
|
+
input: undefined,
|
|
181
|
+
ctx
|
|
182
|
+
})
|
|
183
|
+
});
|
|
184
|
+
return;
|
|
157
185
|
}
|
|
158
186
|
if (msg.method === 'subscription.stop') {
|
|
159
187
|
clientSubscriptions.get(id)?.abort();
|
|
@@ -162,20 +190,24 @@ function getWSConnectionHandler(opts) {
|
|
|
162
190
|
const { path, lastEventId } = msg.params;
|
|
163
191
|
let { input } = msg.params;
|
|
164
192
|
const type = msg.method;
|
|
165
|
-
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
input
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
193
|
+
if (lastEventId !== undefined) {
|
|
194
|
+
if (isObject(input)) {
|
|
195
|
+
input = {
|
|
196
|
+
...input,
|
|
197
|
+
lastEventId: lastEventId
|
|
198
|
+
};
|
|
199
|
+
} else {
|
|
200
|
+
input ?? (input = {
|
|
201
|
+
lastEventId: lastEventId
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
run(async ()=>{
|
|
206
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
207
|
+
const res = await ctxPromise; // asserts context has been set
|
|
208
|
+
if (!res.ok) {
|
|
209
|
+
throw res.error;
|
|
177
210
|
}
|
|
178
|
-
await ctxPromise; // asserts context has been set
|
|
179
211
|
const abortController = new AbortController();
|
|
180
212
|
const result = await callProcedure({
|
|
181
213
|
router,
|
|
@@ -343,7 +375,7 @@ function getWSConnectionHandler(opts) {
|
|
|
343
375
|
type: 'started'
|
|
344
376
|
}
|
|
345
377
|
});
|
|
346
|
-
}
|
|
378
|
+
}).catch((cause)=>{
|
|
347
379
|
// procedure threw an error
|
|
348
380
|
const error = getTRPCErrorFromUnknown(cause);
|
|
349
381
|
opts.onError?.({
|
|
@@ -366,9 +398,9 @@ function getWSConnectionHandler(opts) {
|
|
|
366
398
|
ctx
|
|
367
399
|
})
|
|
368
400
|
});
|
|
369
|
-
}
|
|
401
|
+
});
|
|
370
402
|
}
|
|
371
|
-
client.on('message',
|
|
403
|
+
client.on('message', (rawData)=>{
|
|
372
404
|
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
373
405
|
const msgStr = rawData.toString();
|
|
374
406
|
if (msgStr === 'PONG') {
|
|
@@ -380,7 +412,7 @@ function getWSConnectionHandler(opts) {
|
|
|
380
412
|
}
|
|
381
413
|
return;
|
|
382
414
|
}
|
|
383
|
-
if (ctxPromise
|
|
415
|
+
if (!ctxPromise) {
|
|
384
416
|
// If the ctxPromise wasn't created immediately, we're expecting the first message to be a TRPCConnectionParamsMessage
|
|
385
417
|
ctxPromise = createCtxPromise(()=>{
|
|
386
418
|
let msg;
|
|
@@ -401,30 +433,33 @@ function getWSConnectionHandler(opts) {
|
|
|
401
433
|
});
|
|
402
434
|
return;
|
|
403
435
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
msgJSON
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
436
|
+
const parsedMsgs = run(()=>{
|
|
437
|
+
try {
|
|
438
|
+
const msgJSON = JSON.parse(msgStr);
|
|
439
|
+
const msgs = Array.isArray(msgJSON) ? msgJSON : [
|
|
440
|
+
msgJSON
|
|
441
|
+
];
|
|
442
|
+
return msgs.map((raw)=>parseTRPCMessage(raw, transformer));
|
|
443
|
+
} catch (cause) {
|
|
444
|
+
const error = new TRPCError({
|
|
445
|
+
code: 'PARSE_ERROR',
|
|
446
|
+
cause
|
|
447
|
+
});
|
|
448
|
+
respond({
|
|
449
|
+
id: null,
|
|
450
|
+
error: getErrorShape({
|
|
451
|
+
config: router._def._config,
|
|
452
|
+
error,
|
|
453
|
+
type: 'unknown',
|
|
454
|
+
path: undefined,
|
|
455
|
+
input: undefined,
|
|
456
|
+
ctx
|
|
457
|
+
})
|
|
458
|
+
});
|
|
459
|
+
return [];
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
parsedMsgs.map(handleRequest);
|
|
428
463
|
});
|
|
429
464
|
// WebSocket errors should be handled, as otherwise unhandled exceptions will crash Node.js.
|
|
430
465
|
// This line was introduced after the following error brought down production systems:
|
|
@@ -447,9 +482,6 @@ function getWSConnectionHandler(opts) {
|
|
|
447
482
|
clientSubscriptions.clear();
|
|
448
483
|
abortController.abort();
|
|
449
484
|
});
|
|
450
|
-
if (ctxPromise !== unsetContextPromiseSymbol) {
|
|
451
|
-
await ctxPromise;
|
|
452
|
-
}
|
|
453
485
|
};
|
|
454
486
|
}
|
|
455
487
|
/**
|
|
@@ -486,21 +518,7 @@ function applyWSSHandler(opts) {
|
|
|
486
518
|
if (opts.prefix && !req.url?.startsWith(opts.prefix)) {
|
|
487
519
|
return;
|
|
488
520
|
}
|
|
489
|
-
onConnection(client, req)
|
|
490
|
-
opts.onError?.({
|
|
491
|
-
error: new TRPCError({
|
|
492
|
-
code: 'INTERNAL_SERVER_ERROR',
|
|
493
|
-
cause,
|
|
494
|
-
message: 'Failed to handle WebSocket connection'
|
|
495
|
-
}),
|
|
496
|
-
req: req,
|
|
497
|
-
path: undefined,
|
|
498
|
-
type: 'unknown',
|
|
499
|
-
ctx: undefined,
|
|
500
|
-
input: undefined
|
|
501
|
-
});
|
|
502
|
-
client.close();
|
|
503
|
-
});
|
|
521
|
+
onConnection(client, req);
|
|
504
522
|
});
|
|
505
523
|
return {
|
|
506
524
|
broadcastReconnectNotification: ()=>{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trpc/server",
|
|
3
|
-
"version": "11.1.
|
|
3
|
+
"version": "11.1.1",
|
|
4
4
|
"description": "The tRPC server library",
|
|
5
5
|
"author": "KATT",
|
|
6
6
|
"license": "MIT",
|
|
@@ -152,5 +152,5 @@
|
|
|
152
152
|
"peerDependencies": {
|
|
153
153
|
"typescript": ">=5.7.2"
|
|
154
154
|
},
|
|
155
|
-
"gitHead": "
|
|
155
|
+
"gitHead": "9c1c753a778ac7a2709b1e81be392231c4657319"
|
|
156
156
|
}
|
|
@@ -68,8 +68,8 @@ export function fastifyTRPCPlugin<TRouter extends AnyRouter>(
|
|
|
68
68
|
...trpcOptions,
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
-
fastify.get(prefix ?? '/', { websocket: true },
|
|
72
|
-
|
|
71
|
+
fastify.get(prefix ?? '/', { websocket: true }, (socket, req) => {
|
|
72
|
+
onConnection(socket, req.raw);
|
|
73
73
|
if (trpcOptions?.keepAlive?.enabled) {
|
|
74
74
|
const { pingMs, pongWaitMs } = trpcOptions.keepAlive;
|
|
75
75
|
handleKeepAlive(socket, pingMs, pongWaitMs);
|
package/src/adapters/ws.ts
CHANGED
|
@@ -34,6 +34,8 @@ import {
|
|
|
34
34
|
type MaybePromise,
|
|
35
35
|
} from '../unstable-core-do-not-import';
|
|
36
36
|
// eslint-disable-next-line no-restricted-imports
|
|
37
|
+
import type { Result } from '../unstable-core-do-not-import';
|
|
38
|
+
// eslint-disable-next-line no-restricted-imports
|
|
37
39
|
import { iteratorResource } from '../unstable-core-do-not-import/stream/utils/asyncIterable';
|
|
38
40
|
import { Unpromise } from '../vendor/unpromise';
|
|
39
41
|
import { createURL, type NodeHTTPCreateContextFnOptions } from './node-http';
|
|
@@ -98,14 +100,16 @@ export type WSSHandlerOptions<TRouter extends AnyRouter> =
|
|
|
98
100
|
dangerouslyDisablePong?: boolean;
|
|
99
101
|
};
|
|
100
102
|
|
|
101
|
-
const unsetContextPromiseSymbol = Symbol('unsetContextPromise');
|
|
102
103
|
export function getWSConnectionHandler<TRouter extends AnyRouter>(
|
|
103
104
|
opts: WSSHandlerOptions<TRouter>,
|
|
104
105
|
) {
|
|
105
106
|
const { createContext, router } = opts;
|
|
106
107
|
const { transformer } = router._def._config;
|
|
107
108
|
|
|
108
|
-
return
|
|
109
|
+
return (client: ws.WebSocket, req: IncomingMessage) => {
|
|
110
|
+
type Context = inferRouterContext<TRouter>;
|
|
111
|
+
type ContextResult = Result<Context>;
|
|
112
|
+
|
|
109
113
|
const clientSubscriptions = new Map<number | string, AbortController>();
|
|
110
114
|
const abortController = new AbortController();
|
|
111
115
|
|
|
@@ -122,26 +126,31 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
|
|
|
122
126
|
);
|
|
123
127
|
}
|
|
124
128
|
|
|
125
|
-
function createCtxPromise(
|
|
129
|
+
async function createCtxPromise(
|
|
126
130
|
getConnectionParams: () => TRPCRequestInfo['connectionParams'],
|
|
127
|
-
): Promise<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
131
|
+
): Promise<ContextResult> {
|
|
132
|
+
try {
|
|
133
|
+
return await run(async (): Promise<ContextResult> => {
|
|
134
|
+
ctx = await createContext?.({
|
|
135
|
+
req,
|
|
136
|
+
res: client,
|
|
137
|
+
info: {
|
|
138
|
+
connectionParams: getConnectionParams(),
|
|
139
|
+
calls: [],
|
|
140
|
+
isBatchCall: false,
|
|
141
|
+
accept: null,
|
|
142
|
+
type: 'unknown',
|
|
143
|
+
signal: abortController.signal,
|
|
144
|
+
url: null,
|
|
145
|
+
},
|
|
146
|
+
});
|
|
142
147
|
|
|
143
|
-
|
|
144
|
-
|
|
148
|
+
return {
|
|
149
|
+
ok: true,
|
|
150
|
+
value: ctx,
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
} catch (cause) {
|
|
145
154
|
const error = getTRPCErrorFromUnknown(cause);
|
|
146
155
|
opts.onError?.({
|
|
147
156
|
error,
|
|
@@ -167,12 +176,14 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
|
|
|
167
176
|
(globalThis.setImmediate ?? globalThis.setTimeout)(() => {
|
|
168
177
|
client.close();
|
|
169
178
|
});
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
179
|
+
return {
|
|
180
|
+
ok: false,
|
|
181
|
+
error,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
173
184
|
}
|
|
174
185
|
|
|
175
|
-
let ctx:
|
|
186
|
+
let ctx: Context | undefined = undefined;
|
|
176
187
|
|
|
177
188
|
/**
|
|
178
189
|
* promise for initializing the context
|
|
@@ -182,18 +193,40 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
|
|
|
182
193
|
*/
|
|
183
194
|
let ctxPromise =
|
|
184
195
|
createURL(req).searchParams.get('connectionParams') === '1'
|
|
185
|
-
?
|
|
196
|
+
? null
|
|
186
197
|
: createCtxPromise(() => null);
|
|
187
198
|
|
|
188
|
-
|
|
199
|
+
function handleRequest(msg: TRPCClientOutgoingMessage) {
|
|
189
200
|
const { id, jsonrpc } = msg;
|
|
190
201
|
|
|
191
|
-
/* istanbul ignore next -- @preserve */
|
|
192
202
|
if (id === null) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
203
|
+
const error = getTRPCErrorFromUnknown(
|
|
204
|
+
new TRPCError({
|
|
205
|
+
code: 'PARSE_ERROR',
|
|
206
|
+
message: '`id` is required',
|
|
207
|
+
}),
|
|
208
|
+
);
|
|
209
|
+
opts.onError?.({
|
|
210
|
+
error,
|
|
211
|
+
path: undefined,
|
|
212
|
+
type: 'unknown',
|
|
213
|
+
ctx,
|
|
214
|
+
req,
|
|
215
|
+
input: undefined,
|
|
196
216
|
});
|
|
217
|
+
respond({
|
|
218
|
+
id,
|
|
219
|
+
jsonrpc,
|
|
220
|
+
error: getErrorShape({
|
|
221
|
+
config: router._def._config,
|
|
222
|
+
error,
|
|
223
|
+
type: 'unknown',
|
|
224
|
+
path: undefined,
|
|
225
|
+
input: undefined,
|
|
226
|
+
ctx,
|
|
227
|
+
}),
|
|
228
|
+
});
|
|
229
|
+
return;
|
|
197
230
|
}
|
|
198
231
|
if (msg.method === 'subscription.stop') {
|
|
199
232
|
clientSubscriptions.get(id)?.abort();
|
|
@@ -202,20 +235,25 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
|
|
|
202
235
|
const { path, lastEventId } = msg.params;
|
|
203
236
|
let { input } = msg.params;
|
|
204
237
|
const type = msg.method;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
238
|
+
|
|
239
|
+
if (lastEventId !== undefined) {
|
|
240
|
+
if (isObject(input)) {
|
|
241
|
+
input = {
|
|
242
|
+
...input,
|
|
243
|
+
lastEventId: lastEventId,
|
|
244
|
+
};
|
|
245
|
+
} else {
|
|
246
|
+
input ??= {
|
|
247
|
+
lastEventId: lastEventId,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
run(async () => {
|
|
252
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
253
|
+
const res = await ctxPromise!; // asserts context has been set
|
|
254
|
+
if (!res.ok) {
|
|
255
|
+
throw res.error;
|
|
217
256
|
}
|
|
218
|
-
await ctxPromise; // asserts context has been set
|
|
219
257
|
|
|
220
258
|
const abortController = new AbortController();
|
|
221
259
|
const result = await callTRPCProcedure({
|
|
@@ -384,7 +422,7 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
|
|
|
384
422
|
type: 'started',
|
|
385
423
|
},
|
|
386
424
|
});
|
|
387
|
-
}
|
|
425
|
+
}).catch((cause) => {
|
|
388
426
|
// procedure threw an error
|
|
389
427
|
const error = getTRPCErrorFromUnknown(cause);
|
|
390
428
|
opts.onError?.({ error, path, type, ctx, req, input });
|
|
@@ -400,9 +438,9 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
|
|
|
400
438
|
ctx,
|
|
401
439
|
}),
|
|
402
440
|
});
|
|
403
|
-
}
|
|
441
|
+
});
|
|
404
442
|
}
|
|
405
|
-
client.on('message',
|
|
443
|
+
client.on('message', (rawData) => {
|
|
406
444
|
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
407
445
|
const msgStr = rawData.toString();
|
|
408
446
|
if (msgStr === 'PONG') {
|
|
@@ -414,7 +452,7 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
|
|
|
414
452
|
}
|
|
415
453
|
return;
|
|
416
454
|
}
|
|
417
|
-
if (ctxPromise
|
|
455
|
+
if (!ctxPromise) {
|
|
418
456
|
// If the ctxPromise wasn't created immediately, we're expecting the first message to be a TRPCConnectionParamsMessage
|
|
419
457
|
ctxPromise = createCtxPromise(() => {
|
|
420
458
|
let msg;
|
|
@@ -438,31 +476,36 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
|
|
|
438
476
|
});
|
|
439
477
|
return;
|
|
440
478
|
}
|
|
441
|
-
try {
|
|
442
|
-
const msgJSON: unknown = JSON.parse(msgStr);
|
|
443
|
-
const msgs: unknown[] = Array.isArray(msgJSON) ? msgJSON : [msgJSON];
|
|
444
|
-
const promises = msgs
|
|
445
|
-
.map((raw) => parseTRPCMessage(raw, transformer))
|
|
446
|
-
.map(handleRequest);
|
|
447
|
-
await Promise.all(promises);
|
|
448
|
-
} catch (cause) {
|
|
449
|
-
const error = new TRPCError({
|
|
450
|
-
code: 'PARSE_ERROR',
|
|
451
|
-
cause,
|
|
452
|
-
});
|
|
453
479
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
480
|
+
const parsedMsgs = run(() => {
|
|
481
|
+
try {
|
|
482
|
+
const msgJSON: unknown = JSON.parse(msgStr);
|
|
483
|
+
const msgs: unknown[] = Array.isArray(msgJSON) ? msgJSON : [msgJSON];
|
|
484
|
+
|
|
485
|
+
return msgs.map((raw) => parseTRPCMessage(raw, transformer));
|
|
486
|
+
} catch (cause) {
|
|
487
|
+
const error = new TRPCError({
|
|
488
|
+
code: 'PARSE_ERROR',
|
|
489
|
+
cause,
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
respond({
|
|
493
|
+
id: null,
|
|
494
|
+
error: getErrorShape({
|
|
495
|
+
config: router._def._config,
|
|
496
|
+
error,
|
|
497
|
+
type: 'unknown',
|
|
498
|
+
path: undefined,
|
|
499
|
+
input: undefined,
|
|
500
|
+
ctx,
|
|
501
|
+
}),
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
return [];
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
parsedMsgs.map(handleRequest);
|
|
466
509
|
});
|
|
467
510
|
|
|
468
511
|
// WebSocket errors should be handled, as otherwise unhandled exceptions will crash Node.js.
|
|
@@ -487,10 +530,6 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
|
|
|
487
530
|
clientSubscriptions.clear();
|
|
488
531
|
abortController.abort();
|
|
489
532
|
});
|
|
490
|
-
|
|
491
|
-
if (ctxPromise !== unsetContextPromiseSymbol) {
|
|
492
|
-
await ctxPromise;
|
|
493
|
-
}
|
|
494
533
|
};
|
|
495
534
|
}
|
|
496
535
|
|
|
@@ -544,22 +583,7 @@ export function applyWSSHandler<TRouter extends AnyRouter>(
|
|
|
544
583
|
return;
|
|
545
584
|
}
|
|
546
585
|
|
|
547
|
-
onConnection(client, req)
|
|
548
|
-
opts.onError?.({
|
|
549
|
-
error: new TRPCError({
|
|
550
|
-
code: 'INTERNAL_SERVER_ERROR',
|
|
551
|
-
cause,
|
|
552
|
-
message: 'Failed to handle WebSocket connection',
|
|
553
|
-
}),
|
|
554
|
-
req: req,
|
|
555
|
-
path: undefined,
|
|
556
|
-
type: 'unknown',
|
|
557
|
-
ctx: undefined,
|
|
558
|
-
input: undefined,
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
client.close();
|
|
562
|
-
});
|
|
586
|
+
onConnection(client, req);
|
|
563
587
|
});
|
|
564
588
|
|
|
565
589
|
return {
|