zuplo 6.73.21 → 6.73.23
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.
|
@@ -10,20 +10,25 @@ us to trial this or sign up for an Enterprise account.
|
|
|
10
10
|
|
|
11
11
|
:::
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
:::caution{title="Deprecated"}
|
|
14
|
+
|
|
15
|
+
`webSocketHandler` is deprecated. It's now an alias of
|
|
16
|
+
[`webSocketPipelineHandler`](./websocket-pipeline-handler.mdx), which does
|
|
17
|
+
everything this handler does — with no message policies configured it's the same
|
|
18
|
+
transparent passthrough. Existing routes keep working unchanged; switch the
|
|
19
|
+
handler `export` name to `webSocketPipelineHandler` when convenient.
|
|
20
|
+
|
|
21
|
+
:::
|
|
22
|
+
|
|
23
|
+
The WebSocket Handler proxies WebSocket traffic straight through to your backend
|
|
24
|
+
without inspecting the messages. Use the
|
|
25
|
+
[WebSocket Pipeline Handler](./websocket-pipeline-handler.mdx) when you also
|
|
26
|
+
want to intercept individual messages or run an
|
|
27
|
+
[`onOpen` connection hook](./websocket-pipeline-handler.mdx#the-onopen-connection-hook).
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
The handler can be configured alongside other existing policies like
|
|
25
30
|
[Rate Limiting](../policies/rate-limit-inbound.mdx),
|
|
26
|
-
[API Keys](../policies/api-key-inbound.mdx), etc. and
|
|
31
|
+
[API Keys](../policies/api-key-inbound.mdx), etc. and is available for use on
|
|
27
32
|
all environments.
|
|
28
33
|
|
|
29
34
|
These handlers are only configurable via the JSON View on a project's Route
|
|
@@ -75,9 +80,12 @@ The WebSocket Handler accepts the following options in the `options` property:
|
|
|
75
80
|
- **`rewritePattern`** (required): The URL pattern for the backend WebSocket
|
|
76
81
|
endpoint. Supports JavaScript string interpolation syntax for dynamic URL
|
|
77
82
|
construction based on request data and environment variables.
|
|
78
|
-
- **`policies`** (optional
|
|
79
|
-
|
|
83
|
+
- **`policies`** (optional): Configures the message-interception policies that
|
|
84
|
+
run on each WebSocket frame. See the
|
|
80
85
|
[WebSocket Pipeline Handler](./websocket-pipeline-handler.mdx).
|
|
86
|
+
- **`onOpen`** (optional): Configures a connection hook that runs once per
|
|
87
|
+
connection, before any message is relayed. See
|
|
88
|
+
[the `onOpen` connection hook](./websocket-pipeline-handler.mdx#the-onopen-connection-hook).
|
|
81
89
|
|
|
82
90
|
Similar to other handlers using `rewritePattern`, it supports JavaScript string
|
|
83
91
|
interpolation syntax and can be used to shape the URL based on data from the
|
|
@@ -10,12 +10,24 @@ to trial this or sign up for an Enterprise account.
|
|
|
10
10
|
|
|
11
11
|
:::
|
|
12
12
|
|
|
13
|
-
The `webSocketPipelineHandler`
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
The `webSocketPipelineHandler` is Zuplo's WebSocket handler. It proxies
|
|
14
|
+
WebSocket connections to your backend and optionally lets you run code at two
|
|
15
|
+
points in the connection's life:
|
|
16
|
+
|
|
17
|
+
- **Message policies** pass every WebSocket message through a pipeline of
|
|
18
|
+
**policy functions** before forwarding it. Each policy can inspect, transform,
|
|
19
|
+
or drop the message. Use this to redact sensitive fields, enforce a message
|
|
20
|
+
schema, filter events, or add observability to real-time traffic.
|
|
21
|
+
- **The [`onOpen` connection hook](#the-onopen-connection-hook)** runs once per
|
|
22
|
+
connection, after the connection to your backend is established and before any
|
|
23
|
+
message is relayed. Use it to gate connections, send a handshake message to
|
|
24
|
+
your backend, or attach your own connection lifecycle listeners (such as
|
|
25
|
+
`close`).
|
|
26
|
+
|
|
27
|
+
With no policies and no `onOpen` hook configured, the handler is a transparent
|
|
28
|
+
passthrough — this is what the deprecated
|
|
29
|
+
[`webSocketHandler`](./websocket-handler.mdx) did, and it now resolves to this
|
|
30
|
+
handler.
|
|
19
31
|
|
|
20
32
|
Messages are intercepted in both directions, configured independently:
|
|
21
33
|
|
|
@@ -28,8 +40,10 @@ Messages are intercepted in both directions, configured independently:
|
|
|
28
40
|
|
|
29
41
|
The pipeline only intercepts **message** events. Connection lifecycle events are
|
|
30
42
|
handled automatically: when either side closes, the other side is closed, and
|
|
31
|
-
socket errors are logged and
|
|
32
|
-
`error` events
|
|
43
|
+
socket errors are logged and the connection is torn down. There is no dedicated
|
|
44
|
+
policy hook for `close` or `error` events — instead, use the
|
|
45
|
+
[`onOpen` hook](#the-onopen-connection-hook) to attach your own listeners to
|
|
46
|
+
either socket.
|
|
33
47
|
|
|
34
48
|
:::
|
|
35
49
|
|
|
@@ -187,3 +201,126 @@ You can configure multiple policies in each direction to compose behavior — fo
|
|
|
187
201
|
example, one policy to validate a message against a schema and a second to
|
|
188
202
|
redact fields. Because each policy receives the previous policy's output, order
|
|
189
203
|
matters.
|
|
204
|
+
|
|
205
|
+
## The `onOpen` connection hook
|
|
206
|
+
|
|
207
|
+
The `onOpen` option configures a function that runs **once per connection**,
|
|
208
|
+
after both the client and backend connections are established and **before any
|
|
209
|
+
message is relayed**. Unlike message policies, it isn't tied to a direction — it
|
|
210
|
+
receives both sockets.
|
|
211
|
+
|
|
212
|
+
Configure it at the `options` level, as a sibling of `rewritePattern` and
|
|
213
|
+
`policies`. It works with or without message policies:
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
"handler": {
|
|
217
|
+
"export": "webSocketPipelineHandler",
|
|
218
|
+
"module": "$import(@zuplo/runtime)",
|
|
219
|
+
"options": {
|
|
220
|
+
"rewritePattern": "https://myservice.com/websocket",
|
|
221
|
+
"onOpen": {
|
|
222
|
+
"module": "$import(./modules/websocket-hooks)",
|
|
223
|
+
"export": "onWebSocketOpen"
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The hook matches the `WebSocketOnOpenHook` type exported from `@zuplo/runtime` —
|
|
230
|
+
the same parameters as a message policy, minus the message data:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
|
|
234
|
+
|
|
235
|
+
type WebSocketOnOpenHook = (
|
|
236
|
+
target: WebSocket,
|
|
237
|
+
source: WebSocket,
|
|
238
|
+
request: ZuploRequest,
|
|
239
|
+
context: ZuploContext,
|
|
240
|
+
) => void | Promise<void>;
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
| Parameter | Description |
|
|
244
|
+
| --------- | ------------------------------------------------------------------------------------------------- |
|
|
245
|
+
| `target` | The backend connection. Call `target.send(...)` to send a message to your backend. |
|
|
246
|
+
| `source` | The client connection. Call `source.send(...)` to send a message to the client. |
|
|
247
|
+
| `request` | The original [`ZuploRequest`](../programmable-api/zuplo-request.mdx) from the connection upgrade. |
|
|
248
|
+
| `context` | The [`ZuploContext`](../programmable-api/zuplo-context.mdx) for the connection. |
|
|
249
|
+
|
|
250
|
+
The hook is awaited, and its outcome controls the connection:
|
|
251
|
+
|
|
252
|
+
- **Return (or resolve)** to accept the connection. Message relaying begins only
|
|
253
|
+
after the hook completes — messages that arrive while it runs are buffered and
|
|
254
|
+
delivered afterward, in order.
|
|
255
|
+
- **Throw (or reject)** to abort the connection. Both sockets are closed and the
|
|
256
|
+
client never receives the `101 Switching Protocols` upgrade response.
|
|
257
|
+
|
|
258
|
+
### Example: gate connections and greet the backend
|
|
259
|
+
|
|
260
|
+
This hook rejects unauthenticated connections, then sends a handshake message to
|
|
261
|
+
the backend before any client traffic flows.
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
import { WebSocketOnOpenHook } from "@zuplo/runtime";
|
|
265
|
+
|
|
266
|
+
export const onWebSocketOpen: WebSocketOnOpenHook = async (
|
|
267
|
+
target,
|
|
268
|
+
source,
|
|
269
|
+
request,
|
|
270
|
+
context,
|
|
271
|
+
) => {
|
|
272
|
+
if (!request.user) {
|
|
273
|
+
// Aborts the upgrade: both sockets close and no 101 is returned
|
|
274
|
+
throw new Error("Unauthenticated WebSocket connection");
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Runs before any client message is relayed to the backend
|
|
278
|
+
target.send(
|
|
279
|
+
JSON.stringify({ type: "session.start", user: request.user.sub }),
|
|
280
|
+
);
|
|
281
|
+
};
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Example: react to the connection closing
|
|
285
|
+
|
|
286
|
+
There is no separate `onClose` option — because `onOpen` hands you both raw
|
|
287
|
+
sockets, attach a standard `close` event listener to either one. Listeners
|
|
288
|
+
attached here live for the duration of the connection; there's no need to remove
|
|
289
|
+
them.
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
import { WebSocketOnOpenHook } from "@zuplo/runtime";
|
|
293
|
+
|
|
294
|
+
export const trackConnection: WebSocketOnOpenHook = (
|
|
295
|
+
target,
|
|
296
|
+
source,
|
|
297
|
+
request,
|
|
298
|
+
context,
|
|
299
|
+
) => {
|
|
300
|
+
const connectedAt = Date.now();
|
|
301
|
+
|
|
302
|
+
// Fires when the client disconnects
|
|
303
|
+
source.addEventListener("close", () => {
|
|
304
|
+
const seconds = Math.round((Date.now() - connectedAt) / 1000);
|
|
305
|
+
context.log.info(`Client disconnected after ${seconds}s`);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// Fires when your backend closes the connection
|
|
309
|
+
target.addEventListener("close", () => {
|
|
310
|
+
context.log.warn("Backend closed the WebSocket connection");
|
|
311
|
+
});
|
|
312
|
+
};
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
The same pattern works for `error` and `message` events. For a listener that
|
|
316
|
+
should fire only once — such as waiting for a single handshake reply from your
|
|
317
|
+
backend — pass `{ once: true }` as the third argument to `addEventListener`.
|
|
318
|
+
|
|
319
|
+
:::tip{title="Performance"}
|
|
320
|
+
|
|
321
|
+
With no message policies and no `onOpen` hook, the gateway hands the connection
|
|
322
|
+
straight through to your backend and stays out of the per-message data path
|
|
323
|
+
entirely. Configuring `onOpen` (or any message policy) keeps the gateway in the
|
|
324
|
+
data path for that route, since the hook needs both live sockets to act on.
|
|
325
|
+
|
|
326
|
+
:::
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zuplo",
|
|
3
|
-
"version": "6.73.
|
|
3
|
+
"version": "6.73.23",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The programmable API Gateway",
|
|
6
6
|
"author": "Zuplo, Inc.",
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
"zuplo": "zuplo.js"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@zuplo/cli": "6.73.
|
|
23
|
-
"@zuplo/core": "6.73.
|
|
24
|
-
"@zuplo/runtime": "6.73.
|
|
22
|
+
"@zuplo/cli": "6.73.23",
|
|
23
|
+
"@zuplo/core": "6.73.23",
|
|
24
|
+
"@zuplo/runtime": "6.73.23",
|
|
25
25
|
"@zuplo/test": "1.4.0"
|
|
26
26
|
}
|
|
27
27
|
}
|