@owlmeans/server-socket 0.1.18-rc.20 → 0.1.18-rc.21
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/README.md +11 -6
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/server-socket/SKILL.md +16 -138
- package/build/helper.d.ts +4 -2
- package/build/helper.d.ts.map +1 -1
- package/build/helper.js +15 -0
- package/build/helper.js.map +1 -1
- package/package.json +10 -10
- package/src/helper.ts +37 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Server-side WebSocket connection handler and service for OwlMeans backends.
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
- `
|
|
7
|
+
- `connection(protocol, callback)` creates a typed WebSocket binding for one protocol
|
|
8
8
|
- `appendSocketService()` + `createSocketMiddleware()` wire the WebSocket service into a server context
|
|
9
9
|
- Used in viable for real-time thinking journal updates and file watching
|
|
10
10
|
- The `Connection` interface (from `@owlmeans/socket`) is the runtime object passed to your handler
|
|
@@ -20,10 +20,12 @@ bun add @owlmeans/server-socket@^0.1.18-rc.17
|
|
|
20
20
|
Handle a WebSocket connection on an entrypoint route:
|
|
21
21
|
|
|
22
22
|
```typescript
|
|
23
|
-
import {
|
|
23
|
+
import { connection } from '@owlmeans/server-socket'
|
|
24
|
+
import { bind } from '@owlmeans/server-entrypoint'
|
|
24
25
|
import type { Connection } from '@owlmeans/socket'
|
|
26
|
+
import { appEntrypoints as protocols } from 'my-app-common'
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
const watch = connection(protocols.api.fileWatch, async (conn, context, req) => {
|
|
27
29
|
const ctx = context as Context
|
|
28
30
|
const projectId = req.params.id as string
|
|
29
31
|
|
|
@@ -37,13 +39,16 @@ export const watch = handleConnection<Connection>(async (conn, context, req) =>
|
|
|
37
39
|
conn.notify('file-update', record)
|
|
38
40
|
})
|
|
39
41
|
})
|
|
42
|
+
|
|
43
|
+
export const entrypoints = [bind(protocols.api.fileWatch, watch)]
|
|
40
44
|
```
|
|
41
45
|
|
|
42
46
|
## API
|
|
43
47
|
|
|
44
|
-
### `
|
|
48
|
+
### `connection(protocol, handler): BoundEntrypointHandler`
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
Creates a protocol-bound WebSocket handler. The callback receives `(conn, ctx, req, res)` with
|
|
51
|
+
request and response types inferred from the protocol.
|
|
47
52
|
|
|
48
53
|
```typescript
|
|
49
54
|
handler: (conn: T, ctx: Context, req: AbstractRequest, res: AbstractResponse) => Promise<void>
|
|
@@ -73,7 +78,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
73
78
|
your project's skill store (`.agents/skills/`):
|
|
74
79
|
|
|
75
80
|
```sh
|
|
76
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
81
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.15
|
|
77
82
|
```
|
|
78
83
|
|
|
79
84
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/server-socket",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-09-
|
|
4
|
+
"version": "0.1.18-rc.21",
|
|
5
|
+
"generatedAt": "2026-09-12T08:39:52.732Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -1,149 +1,27 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: server-socket
|
|
3
|
-
description:
|
|
4
|
-
user-invocable: false
|
|
3
|
+
description: Bind OwlMeans socket protocol declarations to Fastify WebSocket handlers.
|
|
5
4
|
---
|
|
6
5
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
7
6
|
|
|
8
|
-
#
|
|
7
|
+
# Socket protocol handlers
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
Declare a socket route and its contract in the shared protocol package. Bind it on the server with
|
|
10
|
+
`connection()` and `bind()`.
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
```ts
|
|
13
|
+
import { bind } from '@owlmeans/server-entrypoint'
|
|
14
|
+
import { connection } from '@owlmeans/server-socket'
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
| `createSocketService(alias?)` | Factory for the WebSocket server service |
|
|
22
|
-
| `appendSocketService(ctx, alias?)` | Register it on the context |
|
|
23
|
-
| `createSocketMiddleware(web?, socket?)` | Loading-stage context middleware that attaches the service to the API server. Marks the config so a re-run does not register twice |
|
|
24
|
-
| `handleConnection(fn)` | Wrap `(conn, ctx, req, res) => Promise<void>` as an entrypoint handler |
|
|
25
|
-
| `SocketService` | Service interface — `update(api: ApiServer)` |
|
|
26
|
-
| `Config` / `Context` | The server config and context types this package expects |
|
|
27
|
-
| Constants | `DEFAULT_ALIAS` (`socket-server`) |
|
|
28
|
-
|
|
29
|
-
## How a socket call is carried
|
|
30
|
-
|
|
31
|
-
An entrypoint declared with `socket(...)` carries `RouteProtocols.SOCKET`, and the protocol is what
|
|
32
|
-
decides the carrier. The HTTP server skips those entrypoints; this service claims them, registering
|
|
33
|
-
each at `entrypoint.mount()` on the same Fastify instance with `websocket: true`. So a declaration
|
|
34
|
-
moves between HTTP and a socket by changing its protocol, and neither the handler nor the caller is
|
|
35
|
-
rewritten.
|
|
36
|
-
|
|
37
|
-
The same enforcement runs first: a `preHandler` hook matches each candidate with
|
|
38
|
-
`entrypoint.route.match(req, entrypoint.mount())`, authorizes it against `getGuards()`, and asserts
|
|
39
|
-
every pair from `getGates()` — inherited guards and gates included — before the connection is handed
|
|
40
|
-
to the handler. A refusal is rendered by the entrypoint's `fixer` when it declares one, so an
|
|
41
|
-
unauthorized upgrade fails the HTTP request rather than opening a socket that answers nothing.
|
|
42
|
-
|
|
43
|
-
The route's `filter` becomes the Fastify schema for the upgrade request — `query`, `params`,
|
|
44
|
-
`headers` — so a malformed connection query is refused before the handler exists.
|
|
45
|
-
|
|
46
|
-
## Usage
|
|
47
|
-
|
|
48
|
-
Wire the carrier once, where the application composes its context:
|
|
49
|
-
|
|
50
|
-
```typescript
|
|
51
|
-
import { appendSocketService, createSocketMiddleware } from '@owlmeans/server-socket'
|
|
52
|
-
|
|
53
|
-
appendSocketService(context)
|
|
54
|
-
context.registerMiddleware(createSocketMiddleware())
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Declare the entrypoint with `socket()` — the `@owlmeans/route` builder that stamps
|
|
58
|
-
`RouteProtocols.SOCKET` on a backend route — in the package both halves import, so the browser
|
|
59
|
-
dials the very declaration the server binds:
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
import { entrypoint } from '@owlmeans/entrypoint'
|
|
63
|
-
import { backend, route, socket } from '@owlmeans/route'
|
|
64
|
-
|
|
65
|
-
export const entrypoints = [
|
|
66
|
-
entrypoint(route(PROJECT, '/project', backend())),
|
|
67
|
-
entrypoint(route(PROJECT_STREAM, '/watch', socket(PROJECT))),
|
|
68
|
-
]
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
Then attach the handler with `elevate`, exactly as for an HTTP entrypoint — the protocol is what
|
|
72
|
-
sends it to this carrier instead of to the HTTP server. Nothing is served until this step runs:
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
import { elevate } from '@owlmeans/server-entrypoint'
|
|
76
|
-
import { handleConnection } from '@owlmeans/server-socket'
|
|
77
|
-
import { MessageType } from '@owlmeans/socket'
|
|
78
|
-
import type { EventMessage } from '@owlmeans/socket'
|
|
79
|
-
|
|
80
|
-
elevate(entrypoints, PROJECT_STREAM, handleConnection(async (conn, ctx, req) => {
|
|
81
|
-
const stop = await subscribeSomewhere(async value => await conn.notify('update', value))
|
|
82
|
-
|
|
83
|
-
conn.listen(async message => {
|
|
84
|
-
const msg = message as EventMessage<{ code: number }>
|
|
85
|
-
if (typeof message === 'object' && msg.type === MessageType.System && msg.event === 'close') {
|
|
86
|
-
await stop()
|
|
16
|
+
export const appEntrypoints = [
|
|
17
|
+
bind(streamEntrypoints.watch, connection(streamEntrypoints.watch,
|
|
18
|
+
async (connection, context, request) => {
|
|
19
|
+
await connection.send({ type: 'ready' })
|
|
87
20
|
}
|
|
88
|
-
|
|
89
|
-
|
|
21
|
+
)),
|
|
22
|
+
]
|
|
90
23
|
```
|
|
91
24
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
A long-lived subscription therefore returns without resolving, and lives on the listeners it
|
|
96
|
-
registered.
|
|
97
|
-
|
|
98
|
-
## Who the connection belongs to
|
|
99
|
-
|
|
100
|
-
A WebSocket handshake carries no Authorization header a browser can set, so the token travels as
|
|
101
|
-
the `AUTH_QUERY` query parameter. The carrier reads it lazily, on the first frame in either
|
|
102
|
-
direction, and unseals it as an envelope: an `Auth` or `AuthCredentials` payload becomes the
|
|
103
|
-
connection's subject, anything else leaves it anonymous.
|
|
104
|
-
|
|
105
|
-
That subject is what stamps frames, as `profileId ?? userId`: an arriving frame is given it as
|
|
106
|
-
`recipient` and the service's own name as `sender`, a departing one is given it as `sender`. It is
|
|
107
|
-
the same pair every ownership rule in the framework keys on, so one profile of a multi-profile
|
|
108
|
-
account never reads another's stream.
|
|
109
|
-
|
|
110
|
-
`conn.authenticate(stage, payload)` runs the in-band sequence instead:
|
|
111
|
-
|
|
112
|
-
| Stage and payload | Answer |
|
|
113
|
-
|---|---|
|
|
114
|
-
| `Authenticate` with an `AuthToken` | Checked against the context's auth service. Its answer is unsealed as an envelope to become the subject, and the reply carries `Authenticated`. A null answer throws `SocketUnauthorized` |
|
|
115
|
-
| `Authenticate` with an already-minted `Auth` | Taken as the subject as it stands, and answered with the SAME stage — not `Authenticated` |
|
|
116
|
-
| `Authenticated` | Answers the subject already captured, or throws `SocketUnauthorized` when there is none |
|
|
117
|
-
| anything else | `SocketUnsupported('auth-service')` — including a payload at `Authenticate` that is neither of the two above |
|
|
118
|
-
|
|
119
|
-
## Liveness and shutdown
|
|
120
|
-
|
|
121
|
-
- A protocol-level ping (the WebSocket control frame) is answered with a pong and goes no further.
|
|
122
|
-
- A JSON `{ type: 'ping' }` frame is answered with a protocol pong and a `{ type: 'pong' }` frame,
|
|
123
|
-
and is then handed to the connection model like any other frame. Its `type` matches no
|
|
124
|
-
`MessageType`, so it is routed nowhere, but it still reaches every `conn.listen` listener as an
|
|
125
|
-
object whose `type` is `'ping'` — a listener has to recognise the frames it wants rather than
|
|
126
|
-
assume every one it is given is a message.
|
|
127
|
-
- A closing socket reaches the handler as a system frame — `MessageType.System`, `event: 'close'`,
|
|
128
|
-
payload `{ code }` — delivered to every `conn.listen` listener. It is the only disconnect notice,
|
|
129
|
-
so every subscription a handler opened is released there.
|
|
130
|
-
- Fastify's `preClose` closes every live connection with code 1001 before the server stops, so a
|
|
131
|
-
rollout does not leave sockets hanging on a process that is going away.
|
|
132
|
-
|
|
133
|
-
## Depends On
|
|
134
|
-
|
|
135
|
-
- `@owlmeans/socket` — `createBasicConnection`, `MessageType`, the error classes
|
|
136
|
-
- `@owlmeans/entrypoint`, `@owlmeans/server-entrypoint` — `getGuards` / `getGates`, `mount()`
|
|
137
|
-
- `@owlmeans/server-api` — the Fastify instance and its request pipeline (`/utils`)
|
|
138
|
-
- `@owlmeans/server-auth` — the auth service the in-band sequence calls
|
|
139
|
-
- `@owlmeans/auth` — `AUTH_QUERY`, `AuthenticationStage`, and the `isAuth` / `isAuthToken` guards
|
|
140
|
-
- `@owlmeans/basic-envelope` — reading the token off the connection query
|
|
141
|
-
- `@owlmeans/context`, `@owlmeans/server-context` — service registration and the config type
|
|
142
|
-
- `@fastify/websocket` (runtime), `fastify` (peer)
|
|
143
|
-
|
|
144
|
-
## Related
|
|
145
|
-
|
|
146
|
-
- `socket` — the message model, and the verbs a handler answers with
|
|
147
|
-
- `route` — `socket()`, and what a route's protocol decides
|
|
148
|
-
- `client-socket` — the browser carrier that dials these entrypoints
|
|
149
|
-
- `server-job` — `watchJobs`, a worked handler built on `handleConnection`
|
|
25
|
+
The handler receives a typed connection, context, request and response boundary. Guards and gates
|
|
26
|
+
are inherited from the protocol tree and enforced before the WebSocket handler runs. Do not expose
|
|
27
|
+
Fastify request objects through a shared contract.
|
package/build/helper.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type { AbstractRequest, AbstractResponse } from '@owlmeans/entrypoint';
|
|
1
|
+
import type { AbstractRequest, AbstractResponse, EntrypointProtocolDeclaration, HandlerRequest, RequestOf, ResponseOf } from '@owlmeans/entrypoint';
|
|
2
2
|
import type { Context } from '@owlmeans/server-api';
|
|
3
|
-
import type { RefedEntrypointHandler } from '@owlmeans/server-entrypoint';
|
|
3
|
+
import type { BoundEntrypointHandler, RefedEntrypointHandler } from '@owlmeans/server-entrypoint';
|
|
4
4
|
import type { Connection } from '@owlmeans/socket';
|
|
5
5
|
import type { WebSocket } from '@fastify/websocket';
|
|
6
|
+
/** Bind a socket protocol to its connection lifecycle without an alias lookup. */
|
|
7
|
+
export declare const connection: <Protocol extends EntrypointProtocolDeclaration, SocketContext extends Context = Context, SocketConnection extends Connection = Connection>(protocol: Protocol, handler: (connection: SocketConnection, context: SocketContext, request: HandlerRequest<RequestOf<Protocol>>, response: AbstractResponse<ResponseOf<Protocol>>) => Promise<void>) => BoundEntrypointHandler<Protocol>;
|
|
6
8
|
export declare const handleConnection: <T extends Connection = Connection>(handler: (conn: T, ctx: Context, req: AbstractRequest<WebSocket>, res: AbstractResponse<any>) => Promise<void>) => RefedEntrypointHandler<AbstractResponse<any>>;
|
|
7
9
|
//# sourceMappingURL=helper.d.ts.map
|
package/build/helper.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EAAE,gBAAgB,EAAE,6BAA6B,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,EACxG,MAAM,sBAAsB,CAAA;AAC7B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,KAAK,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AACjG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAGnD,kFAAkF;AAClF,eAAO,MAAM,UAAU,GACrB,QAAQ,SAAS,6BAA6B,EAC9C,aAAa,SAAS,OAAO,GAAG,OAAO,EACvC,gBAAgB,SAAS,UAAU,GAAG,UAAU,YAEtC,QAAQ,WACT,CACP,UAAU,EAAE,gBAAgB,EAC5B,OAAO,EAAE,aAAa,EACtB,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,EAC5C,QAAQ,EAAE,gBAAgB,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,KAC7C,OAAO,CAAC,IAAI,CAAC,KACjB,sBAAsB,CAAC,QAAQ,CAkBhC,CAAA;AAEF,eAAO,MAAM,gBAAgB,EAAE,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,EAC/D,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,eAAe,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,gBAAgB,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,KAC3G,sBAAsB,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAUhD,CAAA"}
|
package/build/helper.js
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { assertContext } from '@owlmeans/context';
|
|
2
2
|
import { makeConnection } from './utils/connection.js';
|
|
3
|
+
/** Bind a socket protocol to its connection lifecycle without an alias lookup. */
|
|
4
|
+
export const connection = (protocol, handler) => ({
|
|
5
|
+
protocol,
|
|
6
|
+
bind: ref => async (request, response) => {
|
|
7
|
+
const context = assertContext(ref.ref?.ctx, protocol.alias);
|
|
8
|
+
try {
|
|
9
|
+
await handler(makeConnection(request, context), context, request, response);
|
|
10
|
+
response.resolve(undefined);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
response.reject(error);
|
|
14
|
+
}
|
|
15
|
+
return response.value;
|
|
16
|
+
},
|
|
17
|
+
});
|
|
3
18
|
export const handleConnection = handler => ref => async (req, res) => {
|
|
4
19
|
const ctx = assertContext(ref.ref?.ctx);
|
|
5
20
|
try {
|
package/build/helper.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"helper.js","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAQjD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAEtD,kFAAkF;AAClF,MAAM,CAAC,MAAM,UAAU,GAAG,CAKxB,QAAkB,EAClB,OAKkB,EACgB,EAAE,CAAC,CAAC;IACtC,QAAQ;IACR,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE;QACvC,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAkB,CAAA;QAC5E,IAAI,CAAC;YACH,MAAM,OAAO,CACX,cAAc,CAAC,OAAqC,EAAE,OAAO,CAAqB,EAClF,OAAO,EACP,OAAyD,EACzD,QAAkD,CACnD,CAAA;YACD,QAAQ,CAAC,OAAO,CAAC,SAAiC,CAAC,CAAA;QACrD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,MAAM,CAAC,KAAc,CAAC,CAAA;QACjC,CAAC;QAED,OAAO,QAAQ,CAAC,KAAK,CAAA;IACvB,CAAC;CACF,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAEwB,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;IACxF,MAAM,GAAG,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAY,CAAA;IAClD,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,cAAc,CAAC,GAAG,EAAE,GAAG,CAAQ,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;IAC/D,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,GAAG,CAAC,MAAM,CAAC,CAAU,CAAC,CAAA;IACxB,CAAC;IAED,yDAAyD;IACzD,OAAO,GAAG,CAAC,KAAK,CAAA;AAClB,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/server-socket",
|
|
3
|
-
"version": "0.1.18-rc.
|
|
3
|
+
"version": "0.1.18-rc.21",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -31,15 +31,15 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@fastify/websocket": "^11.0.1",
|
|
34
|
-
"@owlmeans/auth": "^0.1.18-rc.
|
|
35
|
-
"@owlmeans/basic-envelope": "^0.1.18-rc.
|
|
36
|
-
"@owlmeans/context": "^0.1.18-rc.
|
|
37
|
-
"@owlmeans/entrypoint": "^0.1.18-rc.
|
|
38
|
-
"@owlmeans/server-api": "^0.1.18-rc.
|
|
39
|
-
"@owlmeans/server-auth": "^0.1.18-rc.
|
|
40
|
-
"@owlmeans/server-context": "^0.1.18-rc.
|
|
41
|
-
"@owlmeans/server-entrypoint": "^0.1.18-rc.
|
|
42
|
-
"@owlmeans/socket": "^0.1.18-rc.
|
|
34
|
+
"@owlmeans/auth": "^0.1.18-rc.12",
|
|
35
|
+
"@owlmeans/basic-envelope": "^0.1.18-rc.15",
|
|
36
|
+
"@owlmeans/context": "^0.1.18-rc.11",
|
|
37
|
+
"@owlmeans/entrypoint": "^0.1.18-rc.14",
|
|
38
|
+
"@owlmeans/server-api": "^0.1.18-rc.20",
|
|
39
|
+
"@owlmeans/server-auth": "^0.1.18-rc.21",
|
|
40
|
+
"@owlmeans/server-context": "^0.1.18-rc.15",
|
|
41
|
+
"@owlmeans/server-entrypoint": "^0.1.18-rc.14",
|
|
42
|
+
"@owlmeans/socket": "^0.1.18-rc.12"
|
|
43
43
|
},
|
|
44
44
|
"publishConfig": {
|
|
45
45
|
"access": "public"
|
package/src/helper.ts
CHANGED
|
@@ -1,11 +1,46 @@
|
|
|
1
1
|
import { assertContext } from '@owlmeans/context'
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
AbstractRequest, AbstractResponse, EntrypointProtocolDeclaration, HandlerRequest, RequestOf, ResponseOf,
|
|
4
|
+
} from '@owlmeans/entrypoint'
|
|
3
5
|
import type { Context } from '@owlmeans/server-api'
|
|
4
|
-
import type { RefedEntrypointHandler } from '@owlmeans/server-entrypoint'
|
|
6
|
+
import type { BoundEntrypointHandler, RefedEntrypointHandler } from '@owlmeans/server-entrypoint'
|
|
5
7
|
import type { Connection } from '@owlmeans/socket'
|
|
6
8
|
import type { WebSocket } from '@fastify/websocket'
|
|
7
9
|
import { makeConnection } from './utils/connection.js'
|
|
8
10
|
|
|
11
|
+
/** Bind a socket protocol to its connection lifecycle without an alias lookup. */
|
|
12
|
+
export const connection = <
|
|
13
|
+
Protocol extends EntrypointProtocolDeclaration,
|
|
14
|
+
SocketContext extends Context = Context,
|
|
15
|
+
SocketConnection extends Connection = Connection,
|
|
16
|
+
>(
|
|
17
|
+
protocol: Protocol,
|
|
18
|
+
handler: (
|
|
19
|
+
connection: SocketConnection,
|
|
20
|
+
context: SocketContext,
|
|
21
|
+
request: HandlerRequest<RequestOf<Protocol>>,
|
|
22
|
+
response: AbstractResponse<ResponseOf<Protocol>>,
|
|
23
|
+
) => Promise<void>,
|
|
24
|
+
): BoundEntrypointHandler<Protocol> => ({
|
|
25
|
+
protocol,
|
|
26
|
+
bind: ref => async (request, response) => {
|
|
27
|
+
const context = assertContext(ref.ref?.ctx, protocol.alias) as SocketContext
|
|
28
|
+
try {
|
|
29
|
+
await handler(
|
|
30
|
+
makeConnection(request as AbstractRequest<WebSocket>, context) as SocketConnection,
|
|
31
|
+
context,
|
|
32
|
+
request as unknown as HandlerRequest<RequestOf<Protocol>>,
|
|
33
|
+
response as AbstractResponse<ResponseOf<Protocol>>,
|
|
34
|
+
)
|
|
35
|
+
response.resolve(undefined as ResponseOf<Protocol>)
|
|
36
|
+
} catch (error) {
|
|
37
|
+
response.reject(error as Error)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return response.value
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
|
|
9
44
|
export const handleConnection: <T extends Connection = Connection>(
|
|
10
45
|
handler: (conn: T, ctx: Context, req: AbstractRequest<WebSocket>, res: AbstractResponse<any>) => Promise<void>
|
|
11
46
|
) => RefedEntrypointHandler<AbstractResponse<any>> = handler => ref => async (req, res) => {
|