@trpc/client 11.0.0-rc.528 → 11.0.0-rc.530

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.
@@ -1,20 +1,20 @@
1
1
  {
2
- "bundleSize": 52387,
3
- "bundleOrigSize": 69892,
4
- "bundleReduction": 25.05,
2
+ "bundleSize": 54079,
3
+ "bundleOrigSize": 71745,
4
+ "bundleReduction": 24.62,
5
5
  "modules": [
6
6
  {
7
7
  "id": "/src/links/wsLink.ts",
8
- "size": 13436,
9
- "origSize": 15060,
8
+ "size": 15128,
9
+ "origSize": 16913,
10
10
  "renderedExports": [
11
11
  "createWSClient",
12
12
  "wsLink"
13
13
  ],
14
14
  "removedExports": [],
15
15
  "dependents": [],
16
- "percent": 25.65,
17
- "reduction": 10.78
16
+ "percent": 27.97,
17
+ "reduction": 10.55
18
18
  },
19
19
  {
20
20
  "id": "/src/links/httpBatchStreamLink.ts",
@@ -25,7 +25,7 @@
25
25
  ],
26
26
  "removedExports": [],
27
27
  "dependents": [],
28
- "percent": 11.19,
28
+ "percent": 10.84,
29
29
  "reduction": 3.51
30
30
  },
31
31
  {
@@ -37,7 +37,7 @@
37
37
  ],
38
38
  "removedExports": [],
39
39
  "dependents": [],
40
- "percent": 10.42,
40
+ "percent": 10.09,
41
41
  "reduction": 18.48
42
42
  },
43
43
  {
@@ -56,12 +56,12 @@
56
56
  ],
57
57
  "removedExports": [],
58
58
  "dependents": [
59
- "/src/links/httpLink.ts",
60
59
  "/src/links/httpBatchLink.ts",
60
+ "/src/links/httpLink.ts",
61
61
  "/src/links/httpBatchStreamLink.ts",
62
62
  "/src/links/httpSubscriptionLink.ts"
63
63
  ],
64
- "percent": 8.52,
64
+ "percent": 8.26,
65
65
  "reduction": 32.82
66
66
  },
67
67
  {
@@ -76,7 +76,7 @@
76
76
  "/src/links/httpBatchLink.ts",
77
77
  "/src/links/httpBatchStreamLink.ts"
78
78
  ],
79
- "percent": 7.8,
79
+ "percent": 7.55,
80
80
  "reduction": 5.64
81
81
  },
82
82
  {
@@ -88,7 +88,7 @@
88
88
  ],
89
89
  "removedExports": [],
90
90
  "dependents": [],
91
- "percent": 7.49,
91
+ "percent": 7.26,
92
92
  "reduction": 4.69
93
93
  },
94
94
  {
@@ -100,7 +100,7 @@
100
100
  ],
101
101
  "removedExports": [],
102
102
  "dependents": [],
103
- "percent": 7.26,
103
+ "percent": 7.04,
104
104
  "reduction": 9.86
105
105
  },
106
106
  {
@@ -112,7 +112,7 @@
112
112
  ],
113
113
  "removedExports": [],
114
114
  "dependents": [],
115
- "percent": 6.08,
115
+ "percent": 5.89,
116
116
  "reduction": 14.15
117
117
  },
118
118
  {
@@ -127,7 +127,7 @@
127
127
  "/src/createTRPCUntypedClient.ts",
128
128
  "/src/createTRPCClient.ts"
129
129
  ],
130
- "percent": 4.13,
130
+ "percent": 4,
131
131
  "reduction": 47.6
132
132
  },
133
133
  {
@@ -140,14 +140,14 @@
140
140
  "removedExports": [],
141
141
  "dependents": [
142
142
  "/src/index.ts",
143
- "/src/links/httpLink.ts",
144
143
  "/src/links/httpBatchLink.ts",
144
+ "/src/links/httpLink.ts",
145
145
  "/src/links/wsLink.ts",
146
146
  "/src/links/httpBatchStreamLink.ts",
147
147
  "/src/links/httpSubscriptionLink.ts",
148
148
  "/src/internals/TRPCUntypedClient.ts"
149
149
  ],
150
- "percent": 3.7,
150
+ "percent": 3.59,
151
151
  "reduction": 45.43
152
152
  },
153
153
  {
@@ -164,7 +164,7 @@
164
164
  "dependents": [
165
165
  "/src/index.ts"
166
166
  ],
167
- "percent": 2.27,
167
+ "percent": 2.19,
168
168
  "reduction": 73.19
169
169
  },
170
170
  {
@@ -179,7 +179,7 @@
179
179
  "/src/links/splitLink.ts",
180
180
  "/src/internals/TRPCUntypedClient.ts"
181
181
  ],
182
- "percent": 1.32,
182
+ "percent": 1.28,
183
183
  "reduction": 32.75
184
184
  },
185
185
  {
@@ -191,7 +191,7 @@
191
191
  ],
192
192
  "removedExports": [],
193
193
  "dependents": [],
194
- "percent": 1.16,
194
+ "percent": 1.13,
195
195
  "reduction": 44.95
196
196
  },
197
197
  {
@@ -205,7 +205,7 @@
205
205
  "dependents": [
206
206
  "/src/unstable-internals.ts"
207
207
  ],
208
- "percent": 1.08,
208
+ "percent": 1.04,
209
209
  "reduction": 66.75
210
210
  },
211
211
  {
@@ -220,7 +220,7 @@
220
220
  "/src/index.ts",
221
221
  "/src/links/internals/httpUtils.ts"
222
222
  ],
223
- "percent": 0.82,
223
+ "percent": 0.79,
224
224
  "reduction": 33.54
225
225
  },
226
226
  {
@@ -234,7 +234,7 @@
234
234
  ],
235
235
  "removedExports": [],
236
236
  "dependents": [],
237
- "percent": 0.63,
237
+ "percent": 0.61,
238
238
  "reduction": 15.17
239
239
  },
240
240
  {
@@ -249,7 +249,7 @@
249
249
  "/src/links/wsLink.ts",
250
250
  "/src/links/httpSubscriptionLink.ts"
251
251
  ],
252
- "percent": 0.3,
252
+ "percent": 0.29,
253
253
  "reduction": 81.71
254
254
  },
255
255
  {
@@ -263,7 +263,7 @@
263
263
  "dependents": [
264
264
  "/src/index.ts"
265
265
  ],
266
- "percent": 0.19,
266
+ "percent": 0.18,
267
267
  "reduction": 82.58
268
268
  },
269
269
  {
@@ -46,6 +46,25 @@ export interface WebSocketClientOptions extends UrlOptionsWithConnectionParams {
46
46
  */
47
47
  closeMs: number;
48
48
  };
49
+ /**
50
+ * Send ping messages to the server and kill the connection if no pong message is returned
51
+ */
52
+ keepAlive?: {
53
+ /**
54
+ * @default false
55
+ */
56
+ enabled: boolean;
57
+ /**
58
+ * Send a ping message every this many milliseconds
59
+ * @default 5_000
60
+ */
61
+ intervalMs?: number;
62
+ /**
63
+ * Close the WebSocket after this many milliseconds if the server does not respond
64
+ * @default 1_000
65
+ */
66
+ pongTimeoutMs?: number;
67
+ };
49
68
  }
50
69
  export declare function createWSClient(opts: WebSocketClientOptions): {
51
70
  close: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"wsLink.d.ts","sourceRoot":"","sources":["../../src/links/wsLink.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAGvE,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAChB,gBAAgB,EAMhB,mBAAmB,EACpB,MAAM,0CAA0C,CAAC;AAElD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,EAEL,KAAK,8BAA8B,EACpC,MAAM,qCAAqC,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAInD,KAAK,gBAAgB,CAAC,OAAO,SAAS,SAAS,EAAE,OAAO,IAAI,mBAAmB,CAC7E,OAAO,EACP,gBAAgB,CAAC,OAAO,CAAC,CAC1B,CAAC;AAEF,KAAK,kBAAkB,CAAC,OAAO,SAAS,SAAS,EAAE,OAAO,IAAI,QAAQ,CACpE,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,EAClC,eAAe,CAAC,OAAO,CAAC,CACzB,CAAC;AAEF,QAAA,MAAM,kBAAkB,iBAAkB,MAAM,WACoB,CAAC;AAErE,MAAM,WAAW,sBAAuB,SAAQ,8BAA8B;IAC5E;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,SAAS,CAAC;IAC7B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,kBAAkB,CAAC;IACzC;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAChC;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC9C;;OAEG;IACH,IAAI,CAAC,EAAE;QACL;;;WAGG;QACH,OAAO,EAAE,OAAO,CAAC;QACjB;;;WAGG;QACH,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAOD,wBAAgB,cAAc,CAAC,IAAI,EAAE,sBAAsB;;oBAqUlC;QACrB,EAAE,EAAE,SAAS,CAAC;QACd,SAAS,yCAAa;QACtB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;KACjC,KAAG,aAAa;;YAxRX,MAAM;;eAGC,MAAM;YACT,SAAS;;eAGN,QAAQ;YACX,SAAS;;eAGN,YAAY;aACd,SAAS;;IA+UlB;;OAEG;;EAGN;AACD,MAAM,MAAM,mBAAmB,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AAEpE,MAAM,MAAM,oBAAoB,CAAC,OAAO,SAAS,SAAS,IAAI;IAC5D,MAAM,EAAE,mBAAmB,CAAC;CAC7B,GAAG,kBAAkB,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;AASlD;;GAEG;AACH,wBAAgB,MAAM,CAAC,OAAO,SAAS,SAAS,EAC9C,IAAI,EAAE,oBAAoB,CAAC,OAAO,CAAC,GAClC,QAAQ,CAAC,OAAO,CAAC,CA+CnB"}
1
+ {"version":3,"file":"wsLink.d.ts","sourceRoot":"","sources":["../../src/links/wsLink.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAGvE,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAChB,gBAAgB,EAMhB,mBAAmB,EACpB,MAAM,0CAA0C,CAAC;AAElD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,EAEL,KAAK,8BAA8B,EACpC,MAAM,qCAAqC,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAInD,KAAK,gBAAgB,CAAC,OAAO,SAAS,SAAS,EAAE,OAAO,IAAI,mBAAmB,CAC7E,OAAO,EACP,gBAAgB,CAAC,OAAO,CAAC,CAC1B,CAAC;AAEF,KAAK,kBAAkB,CAAC,OAAO,SAAS,SAAS,EAAE,OAAO,IAAI,QAAQ,CACpE,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,EAClC,eAAe,CAAC,OAAO,CAAC,CACzB,CAAC;AAEF,QAAA,MAAM,kBAAkB,iBAAkB,MAAM,WACoB,CAAC;AAErE,MAAM,WAAW,sBAAuB,SAAQ,8BAA8B;IAC5E;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,SAAS,CAAC;IAC7B;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,kBAAkB,CAAC;IACzC;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAChC;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC9C;;OAEG;IACH,IAAI,CAAC,EAAE;QACL;;;WAGG;QACH,OAAO,EAAE,OAAO,CAAC;QACjB;;;WAGG;QACH,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF;;OAEG;IACH,SAAS,CAAC,EAAE;QACV;;WAEG;QACH,OAAO,EAAE,OAAO,CAAC;QACjB;;;WAGG;QACH,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB;;;WAGG;QACH,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;CACH;AAOD,wBAAgB,cAAc,CAAC,IAAI,EAAE,sBAAsB;;oBAuXlC;QACrB,EAAE,EAAE,SAAS,CAAC;QACd,SAAS,yCAAa;QACtB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;KACjC,KAAG,aAAa;;YA1UX,MAAM;;eAGC,MAAM;YACT,SAAS;;eAGN,QAAQ;YACX,SAAS;;eAGN,YAAY;aACd,SAAS;;IAiYlB;;OAEG;;EAGN;AACD,MAAM,MAAM,mBAAmB,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AAEpE,MAAM,MAAM,oBAAoB,CAAC,OAAO,SAAS,SAAS,IAAI;IAC5D,MAAM,EAAE,mBAAmB,CAAC;CAC7B,GAAG,kBAAkB,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;AASlD;;GAEG;AACH,wBAAgB,MAAM,CAAC,OAAO,SAAS,SAAS,EAC9C,IAAI,EAAE,oBAAoB,CAAC,OAAO,CAAC,GAClC,QAAQ,CAAC,OAAO,CAAC,CA+CnB"}
@@ -120,12 +120,16 @@ function createWSClient(opts) {
120
120
  }, lazyOpts.closeMs);
121
121
  };
122
122
  function createConnection() {
123
+ let pingTimeout;
124
+ let pongTimeout;
123
125
  const self = {
124
126
  id: ++connectionIndex,
125
127
  state: 'connecting'
126
128
  };
127
129
  clearTimeout(lazyDisconnectTimer);
128
130
  const onCloseOrError = ()=>{
131
+ clearTimeout(pingTimeout);
132
+ clearTimeout(pongTimeout);
129
133
  if (self.state === 'closed') {
130
134
  return;
131
135
  }
@@ -149,6 +153,15 @@ function createWSClient(opts) {
149
153
  }
150
154
  }
151
155
  };
156
+ const onClose = (code)=>{
157
+ const wasOpen = self.state === 'open';
158
+ onCloseOrError();
159
+ if (wasOpen) {
160
+ opts.onClose?.({
161
+ code
162
+ });
163
+ }
164
+ };
152
165
  const onError = (evt)=>{
153
166
  onCloseOrError();
154
167
  opts.onError?.(evt);
@@ -165,17 +178,44 @@ function createWSClient(opts) {
165
178
  clearTimeout(connectTimer);
166
179
  connectTimer = undefined;
167
180
  ws.addEventListener('open', ()=>{
168
- run(async ()=>{
169
- /* istanbul ignore next -- @preserve */ if (activeConnection?.ws !== ws) {
181
+ async function sendConnectionParams() {
182
+ if (!opts.connectionParams) {
183
+ return;
184
+ }
185
+ const connectMsg = {
186
+ method: 'connectionParams',
187
+ data: await urlWithConnectionParams.resultOf(opts.connectionParams)
188
+ };
189
+ ws.send(JSON.stringify(connectMsg));
190
+ }
191
+ function handleKeepAlive() {
192
+ if (!opts.keepAlive?.enabled) {
170
193
  return;
171
194
  }
172
- if (opts.connectionParams) {
173
- const connectMsg = {
174
- method: 'connectionParams',
175
- data: await urlWithConnectionParams.resultOf(opts.connectionParams)
195
+ const { pongTimeoutMs =1000 , intervalMs =5000 } = opts.keepAlive;
196
+ function sendPing() {
197
+ ws.send('PING');
198
+ pongTimeout = setTimeout(()=>{
199
+ ws.close(3001);
200
+ onClose(3001);
201
+ }, pongTimeoutMs);
202
+ const onMessage = (msg)=>{
203
+ if (msg.data === 'PONG') {
204
+ clearTimeout(pongTimeout);
205
+ pingTimeout = setTimeout(sendPing, intervalMs);
206
+ }
207
+ ws.removeEventListener('message', onMessage);
176
208
  };
177
- ws.send(JSON.stringify(connectMsg));
209
+ ws.addEventListener('message', onMessage);
210
+ }
211
+ pingTimeout = setTimeout(sendPing, intervalMs);
212
+ }
213
+ run(async ()=>{
214
+ /* istanbul ignore next -- @preserve */ if (activeConnection?.ws !== ws) {
215
+ return;
178
216
  }
217
+ await sendConnectionParams();
218
+ handleKeepAlive();
179
219
  connectAttempt = 0;
180
220
  self.state = 'open';
181
221
  opts.onOpen?.();
@@ -224,6 +264,9 @@ function createWSClient(opts) {
224
264
  }
225
265
  };
226
266
  ws.addEventListener('message', ({ data })=>{
267
+ if (data === 'PONG') {
268
+ return;
269
+ }
227
270
  startLazyDisconnectTimer();
228
271
  const msg = JSON.parse(data);
229
272
  if ('method' in msg) {
@@ -118,12 +118,16 @@ function createWSClient(opts) {
118
118
  }, lazyOpts.closeMs);
119
119
  };
120
120
  function createConnection() {
121
+ let pingTimeout;
122
+ let pongTimeout;
121
123
  const self = {
122
124
  id: ++connectionIndex,
123
125
  state: 'connecting'
124
126
  };
125
127
  clearTimeout(lazyDisconnectTimer);
126
128
  const onCloseOrError = ()=>{
129
+ clearTimeout(pingTimeout);
130
+ clearTimeout(pongTimeout);
127
131
  if (self.state === 'closed') {
128
132
  return;
129
133
  }
@@ -147,6 +151,15 @@ function createWSClient(opts) {
147
151
  }
148
152
  }
149
153
  };
154
+ const onClose = (code)=>{
155
+ const wasOpen = self.state === 'open';
156
+ onCloseOrError();
157
+ if (wasOpen) {
158
+ opts.onClose?.({
159
+ code
160
+ });
161
+ }
162
+ };
150
163
  const onError = (evt)=>{
151
164
  onCloseOrError();
152
165
  opts.onError?.(evt);
@@ -163,17 +176,44 @@ function createWSClient(opts) {
163
176
  clearTimeout(connectTimer);
164
177
  connectTimer = undefined;
165
178
  ws.addEventListener('open', ()=>{
166
- run(async ()=>{
167
- /* istanbul ignore next -- @preserve */ if (activeConnection?.ws !== ws) {
179
+ async function sendConnectionParams() {
180
+ if (!opts.connectionParams) {
181
+ return;
182
+ }
183
+ const connectMsg = {
184
+ method: 'connectionParams',
185
+ data: await resultOf(opts.connectionParams)
186
+ };
187
+ ws.send(JSON.stringify(connectMsg));
188
+ }
189
+ function handleKeepAlive() {
190
+ if (!opts.keepAlive?.enabled) {
168
191
  return;
169
192
  }
170
- if (opts.connectionParams) {
171
- const connectMsg = {
172
- method: 'connectionParams',
173
- data: await resultOf(opts.connectionParams)
193
+ const { pongTimeoutMs =1000 , intervalMs =5000 } = opts.keepAlive;
194
+ function sendPing() {
195
+ ws.send('PING');
196
+ pongTimeout = setTimeout(()=>{
197
+ ws.close(3001);
198
+ onClose(3001);
199
+ }, pongTimeoutMs);
200
+ const onMessage = (msg)=>{
201
+ if (msg.data === 'PONG') {
202
+ clearTimeout(pongTimeout);
203
+ pingTimeout = setTimeout(sendPing, intervalMs);
204
+ }
205
+ ws.removeEventListener('message', onMessage);
174
206
  };
175
- ws.send(JSON.stringify(connectMsg));
207
+ ws.addEventListener('message', onMessage);
208
+ }
209
+ pingTimeout = setTimeout(sendPing, intervalMs);
210
+ }
211
+ run(async ()=>{
212
+ /* istanbul ignore next -- @preserve */ if (activeConnection?.ws !== ws) {
213
+ return;
176
214
  }
215
+ await sendConnectionParams();
216
+ handleKeepAlive();
177
217
  connectAttempt = 0;
178
218
  self.state = 'open';
179
219
  opts.onOpen?.();
@@ -222,6 +262,9 @@ function createWSClient(opts) {
222
262
  }
223
263
  };
224
264
  ws.addEventListener('message', ({ data })=>{
265
+ if (data === 'PONG') {
266
+ return;
267
+ }
225
268
  startLazyDisconnectTimer();
226
269
  const msg = JSON.parse(data);
227
270
  if ('method' in msg) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trpc/client",
3
- "version": "11.0.0-rc.528+32e6b1285",
3
+ "version": "11.0.0-rc.530+d1e8f33f6",
4
4
  "description": "The tRPC client library",
5
5
  "author": "KATT",
6
6
  "license": "MIT",
@@ -76,10 +76,10 @@
76
76
  "!**/*.test.*"
77
77
  ],
78
78
  "peerDependencies": {
79
- "@trpc/server": "11.0.0-rc.528+32e6b1285"
79
+ "@trpc/server": "11.0.0-rc.530+d1e8f33f6"
80
80
  },
81
81
  "devDependencies": {
82
- "@trpc/server": "11.0.0-rc.528+32e6b1285",
82
+ "@trpc/server": "11.0.0-rc.530+d1e8f33f6",
83
83
  "@types/isomorphic-fetch": "^0.0.39",
84
84
  "@types/node": "^20.10.0",
85
85
  "eslint": "^8.57.0",
@@ -96,5 +96,5 @@
96
96
  "funding": [
97
97
  "https://trpc.io/sponsor"
98
98
  ],
99
- "gitHead": "32e6b1285dd844776d323ae23fbea638312d676e"
99
+ "gitHead": "d1e8f33f6bc6104003f4a0d4030c6cf306e6dec2"
100
100
  }
@@ -74,6 +74,25 @@ export interface WebSocketClientOptions extends UrlOptionsWithConnectionParams {
74
74
  */
75
75
  closeMs: number;
76
76
  };
77
+ /**
78
+ * Send ping messages to the server and kill the connection if no pong message is returned
79
+ */
80
+ keepAlive?: {
81
+ /**
82
+ * @default false
83
+ */
84
+ enabled: boolean;
85
+ /**
86
+ * Send a ping message every this many milliseconds
87
+ * @default 5_000
88
+ */
89
+ intervalMs?: number;
90
+ /**
91
+ * Close the WebSocket after this many milliseconds if the server does not respond
92
+ * @default 1_000
93
+ */
94
+ pongTimeoutMs?: number;
95
+ };
77
96
  }
78
97
 
79
98
  type LazyOptions = Required<NonNullable<WebSocketClientOptions['lazy']>>;
@@ -244,6 +263,8 @@ export function createWSClient(opts: WebSocketClientOptions) {
244
263
  };
245
264
 
246
265
  function createConnection(): Connection {
266
+ let pingTimeout: ReturnType<typeof setTimeout> | undefined;
267
+ let pongTimeout: ReturnType<typeof setTimeout> | undefined;
247
268
  const self: Connection = {
248
269
  id: ++connectionIndex,
249
270
  state: 'connecting',
@@ -252,6 +273,9 @@ export function createWSClient(opts: WebSocketClientOptions) {
252
273
  clearTimeout(lazyDisconnectTimer);
253
274
 
254
275
  const onCloseOrError = () => {
276
+ clearTimeout(pingTimeout);
277
+ clearTimeout(pongTimeout);
278
+
255
279
  if (self.state === 'closed') {
256
280
  return;
257
281
  }
@@ -283,6 +307,15 @@ export function createWSClient(opts: WebSocketClientOptions) {
283
307
  }
284
308
  };
285
309
 
310
+ const onClose = (code: number) => {
311
+ const wasOpen = self.state === 'open';
312
+ onCloseOrError();
313
+
314
+ if (wasOpen) {
315
+ opts.onClose?.({ code });
316
+ }
317
+ };
318
+
286
319
  const onError = (evt?: Event) => {
287
320
  onCloseOrError();
288
321
  opts.onError?.(evt);
@@ -302,19 +335,50 @@ export function createWSClient(opts: WebSocketClientOptions) {
302
335
  connectTimer = undefined;
303
336
 
304
337
  ws.addEventListener('open', () => {
338
+ async function sendConnectionParams() {
339
+ if (!opts.connectionParams) {
340
+ return;
341
+ }
342
+
343
+ const connectMsg: TRPCConnectionParamsMessage = {
344
+ method: 'connectionParams',
345
+ data: await resultOf(opts.connectionParams),
346
+ };
347
+
348
+ ws.send(JSON.stringify(connectMsg));
349
+ }
350
+ function handleKeepAlive() {
351
+ if (!opts.keepAlive?.enabled) {
352
+ return;
353
+ }
354
+ const { pongTimeoutMs = 1_000, intervalMs = 5_000 } = opts.keepAlive;
355
+
356
+ function sendPing() {
357
+ ws.send('PING');
358
+ pongTimeout = setTimeout(() => {
359
+ ws.close(3001);
360
+ onClose(3001);
361
+ }, pongTimeoutMs);
362
+ const onMessage = (msg: MessageEvent) => {
363
+ if (msg.data === 'PONG') {
364
+ clearTimeout(pongTimeout);
365
+ pingTimeout = setTimeout(sendPing, intervalMs);
366
+ }
367
+ ws.removeEventListener('message', onMessage);
368
+ };
369
+ ws.addEventListener('message', onMessage);
370
+ }
371
+ pingTimeout = setTimeout(sendPing, intervalMs);
372
+ }
305
373
  run(async () => {
306
374
  /* istanbul ignore next -- @preserve */
307
375
  if (activeConnection?.ws !== ws) {
308
376
  return;
309
377
  }
310
- if (opts.connectionParams) {
311
- const connectMsg: TRPCConnectionParamsMessage = {
312
- method: 'connectionParams',
313
- data: await resultOf(opts.connectionParams),
314
- };
315
378
 
316
- ws.send(JSON.stringify(connectMsg));
317
- }
379
+ await sendConnectionParams();
380
+
381
+ handleKeepAlive();
318
382
 
319
383
  connectAttempt = 0;
320
384
  self.state = 'open';
@@ -378,7 +442,11 @@ export function createWSClient(opts: WebSocketClientOptions) {
378
442
  req.callbacks.complete();
379
443
  }
380
444
  };
445
+
381
446
  ws.addEventListener('message', ({ data }) => {
447
+ if (data === 'PONG') {
448
+ return;
449
+ }
382
450
  startLazyDisconnectTimer();
383
451
 
384
452
  const msg = JSON.parse(data) as TRPCClientIncomingMessage;
@@ -396,6 +464,7 @@ export function createWSClient(opts: WebSocketClientOptions) {
396
464
 
397
465
  ws.addEventListener('close', ({ code }) => {
398
466
  const wasOpen = self.state === 'open';
467
+
399
468
  onCloseOrError();
400
469
 
401
470
  if (wasOpen) {