@owlmeans/server-socket 0.1.18-rc.5 → 0.1.18-rc.50
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 +173 -37
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/server-socket/SKILL.md +18 -21
- 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/build/service.d.ts.map +1 -1
- package/build/service.js +6 -9
- package/build/service.js.map +1 -1
- package/build/utils/connection.d.ts.map +1 -1
- package/build/utils/connection.js +33 -8
- package/build/utils/connection.js.map +1 -1
- package/package.json +10 -10
- package/src/helper.ts +37 -2
- package/src/service.ts +9 -10
- package/src/utils/connection.ts +34 -8
- package/build/.gitkeep +0 -0
package/README.md
CHANGED
|
@@ -1,69 +1,205 @@
|
|
|
1
1
|
# @owlmeans/server-socket
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
- The `Connection` interface (from `@owlmeans/socket`) is the runtime object passed to your handler
|
|
3
|
+
The server WebSocket carrier for OwlMeans backends: binds socket protocol declarations to connection
|
|
4
|
+
handlers on the Fastify API server. An application uses `connection(protocol, callback)` for push
|
|
5
|
+
and streaming channels — live notifications, file or job progress, RPC over one long-lived
|
|
6
|
+
connection. `@owlmeans/server-app`'s `makeContext` already registers the socket service and its
|
|
7
|
+
middleware. Request/response HTTP routes use `handlers<Context>()` from `@owlmeans/server-api`, the
|
|
8
|
+
browser side is `@owlmeans/client-socket`, and background work that must survive a restart belongs
|
|
9
|
+
in a queue (`@owlmeans/server-job`).
|
|
11
10
|
|
|
12
11
|
## Installation
|
|
13
12
|
|
|
14
13
|
```bash
|
|
15
|
-
bun add @owlmeans/server-socket
|
|
14
|
+
bun add @owlmeans/server-socket@^0.1.18-rc.50
|
|
16
15
|
```
|
|
17
16
|
|
|
17
|
+
## Concepts
|
|
18
|
+
|
|
19
|
+
- **Socket protocol** — a declaration whose route is built with `socket()` from `@owlmeans/route`.
|
|
20
|
+
The HTTP server skips it; the socket service mounts it as a `GET` WebSocket route.
|
|
21
|
+
- **Connection** — the `@owlmeans/socket` `Connection` model. This package assigns its `send`,
|
|
22
|
+
`close`, `authenticate` and `prepare`; the handler uses `notify`, `listen`, `observe`, `perform`,
|
|
23
|
+
`call` and `close`.
|
|
24
|
+
- **Upgrade-time guards** — the protocol's guards and gates run before the upgrade with the same
|
|
25
|
+
pipeline as HTTP, including organization-entity resolution, so a guarded socket arrives with
|
|
26
|
+
`request.auth` and `request.entity` set.
|
|
27
|
+
- **In-band authentication** — a socket that receives its token after the connection is open
|
|
28
|
+
assigns `conn.authenticate`; that code path establishes authentication and must resolve the entity
|
|
29
|
+
itself.
|
|
30
|
+
- **System frames** — on close, listeners receive `{ type: MessageType.System, event: 'close',
|
|
31
|
+
payload: { code } }`. A thrown callback closes the socket with code 1011; server shutdown closes
|
|
32
|
+
open sockets with 1001.
|
|
33
|
+
|
|
18
34
|
## Usage
|
|
19
35
|
|
|
20
|
-
|
|
36
|
+
### Declare and bind
|
|
21
37
|
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
import
|
|
38
|
+
```ts
|
|
39
|
+
// my-app-common
|
|
40
|
+
import { contract, protocol, typed } from '@owlmeans/entrypoint'
|
|
41
|
+
import { route, socket } from '@owlmeans/route'
|
|
42
|
+
|
|
43
|
+
export const projectProtocols = {
|
|
44
|
+
// ...base, list, create
|
|
45
|
+
events: protocol(
|
|
46
|
+
route('my-app:api:project:events', '/:id/events', socket({ parent: projectBase })),
|
|
47
|
+
contract.request({ params: typed<ProjectParams>(ProjectParamsSchema) }, typed<void>()),
|
|
48
|
+
),
|
|
49
|
+
}
|
|
50
|
+
```
|
|
25
51
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
52
|
+
```ts
|
|
53
|
+
// my-app-api
|
|
54
|
+
import { bind } from '@owlmeans/server-app'
|
|
55
|
+
import { connection } from '@owlmeans/server-socket'
|
|
56
|
+
import { projectProtocols } from 'my-app-common'
|
|
57
|
+
import type { Context } from './context.js'
|
|
29
58
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
await
|
|
59
|
+
const events = connection<typeof projectProtocols.events, Context>(projectProtocols.events,
|
|
60
|
+
async (conn, context, request) => {
|
|
61
|
+
await conn.notify('project:ready', { id: request.params.id })
|
|
33
62
|
})
|
|
34
63
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
64
|
+
export const appEntrypoints = [
|
|
65
|
+
bind(projectProtocols.events, events),
|
|
66
|
+
]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Push organization-scoped events and clean up
|
|
70
|
+
|
|
71
|
+
The parent declares `guards`, so authentication and the entity are resolved before the upgrade.
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { requireEntityKey } from '@owlmeans/auth-common'
|
|
75
|
+
import { MessageType } from '@owlmeans/socket'
|
|
76
|
+
import type { EventMessage } from '@owlmeans/socket'
|
|
77
|
+
|
|
78
|
+
export const events = connection<typeof projectProtocols.events, Context>(projectProtocols.events,
|
|
79
|
+
async (conn, context, request) => {
|
|
80
|
+
const entityId = requireEntityKey(request)
|
|
81
|
+
|
|
82
|
+
// app-local pub/sub resource, one channel per organization
|
|
83
|
+
const unsubscribe = await context.projectEvents().subscribe(async event => {
|
|
84
|
+
if (event.projectId === request.params.id) {
|
|
85
|
+
await conn.notify('project:event', event)
|
|
86
|
+
}
|
|
87
|
+
}, { channel: entityId })
|
|
88
|
+
|
|
89
|
+
conn.listen(async raw => {
|
|
90
|
+
if (typeof raw !== 'object') return
|
|
91
|
+
const message = raw as EventMessage<void>
|
|
92
|
+
if (message.type === MessageType.System && message.event === 'close') {
|
|
93
|
+
await unsubscribe()
|
|
94
|
+
}
|
|
95
|
+
})
|
|
38
96
|
})
|
|
39
|
-
})
|
|
40
97
|
```
|
|
41
98
|
|
|
42
|
-
|
|
99
|
+
### Authenticate in-band
|
|
100
|
+
|
|
101
|
+
When the client cannot send a bearer on the upgrade request, it sends the token as the first auth
|
|
102
|
+
frame. The handler runs the guard itself and must call `attachEntity`.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { AUTH_HEADER, AuthenFailed, AuthenticationStage, AuthUnknown } from '@owlmeans/auth'
|
|
106
|
+
import type { Auth, AuthToken } from '@owlmeans/auth'
|
|
107
|
+
import { attachEntity, DEFAULT_GUARD, requireEntityKey } from '@owlmeans/auth-common'
|
|
108
|
+
import { provideResponse } from '@owlmeans/entrypoint'
|
|
109
|
+
import type { AbstractResponse, GuardService } from '@owlmeans/entrypoint'
|
|
110
|
+
import type { AuthenticateMethod } from '@owlmeans/socket'
|
|
43
111
|
|
|
44
|
-
|
|
112
|
+
export const watch = connection<typeof projectProtocols.watch, Context>(projectProtocols.watch,
|
|
113
|
+
async (conn, context, request) => {
|
|
114
|
+
const timeout = setTimeout(() => void conn.close(), 5_000)
|
|
45
115
|
|
|
46
|
-
|
|
116
|
+
conn.authenticate = (async (stage: AuthenticationStage, token: AuthToken) => {
|
|
117
|
+
if (stage !== AuthenticationStage.Authenticate) {
|
|
118
|
+
throw new AuthUnknown('stage')
|
|
119
|
+
}
|
|
47
120
|
|
|
48
|
-
|
|
49
|
-
|
|
121
|
+
request.headers[AUTH_HEADER] ??= token.token
|
|
122
|
+
const response: AbstractResponse<Auth> = provideResponse({ header: () => void 0 })
|
|
123
|
+
const guard = context.service<GuardService>(DEFAULT_GUARD)
|
|
124
|
+
if (!await guard.handle(request, response) || response.value == null) {
|
|
125
|
+
throw new AuthenFailed('guard')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
request.auth = response.value
|
|
129
|
+
// This path establishes authentication, so it resolves the organization entity itself.
|
|
130
|
+
await attachEntity(context, request)
|
|
131
|
+
clearTimeout(timeout)
|
|
132
|
+
|
|
133
|
+
await startWatching(conn, context, requireEntityKey(request))
|
|
134
|
+
|
|
135
|
+
return [AuthenticationStage.Authenticated, true]
|
|
136
|
+
}) as AuthenticateMethod
|
|
137
|
+
})
|
|
50
138
|
```
|
|
51
139
|
|
|
52
|
-
|
|
140
|
+
### RPC over a connection
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
export const terminal = connection<typeof projectProtocols.terminal, Context>(projectProtocols.terminal,
|
|
144
|
+
async (conn, context, request) => {
|
|
145
|
+
const entityId = requireEntityKey(request)
|
|
53
146
|
|
|
54
|
-
|
|
147
|
+
conn.perform('project:rename', async (id: string, name: string) => {
|
|
148
|
+
const project = await context.project().load({ id, entityId })
|
|
149
|
+
return project == null ? null : context.project().update({ ...project, name })
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
```
|
|
55
153
|
|
|
56
|
-
|
|
154
|
+
The browser side calls it with `conn.call('project:rename', id, name)` on a
|
|
155
|
+
`@owlmeans/client-socket` connection.
|
|
57
156
|
|
|
58
|
-
###
|
|
157
|
+
### A context without `@owlmeans/server-app`
|
|
59
158
|
|
|
60
|
-
|
|
159
|
+
```ts
|
|
160
|
+
import { appendApiServer } from '@owlmeans/server-api'
|
|
161
|
+
import { appendSocketService, createSocketMiddleware } from '@owlmeans/server-socket'
|
|
61
162
|
|
|
62
|
-
|
|
163
|
+
appendApiServer(context)
|
|
164
|
+
appendSocketService(context)
|
|
165
|
+
context.registerMiddleware(createSocketMiddleware())
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## API
|
|
63
169
|
|
|
64
|
-
|
|
170
|
+
| Symbol | Kind | Purpose |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| `connection<Protocol, SocketContext, SocketConnection>(protocol, handler)` | function | Bind a socket protocol to `(connection, context, request, response) => Promise<void>`; returns `BoundEntrypointHandler<Protocol>` |
|
|
173
|
+
| `handleConnection(handler)` | function | Unbound compatibility wrapper returning a `RefedEntrypointHandler` |
|
|
174
|
+
| `appendSocketService(ctx, alias = DEFAULT_ALIAS)` | function | Register the socket service on a context that already has the API server |
|
|
175
|
+
| `createSocketService(alias?)` | function | Create the socket service; `update(apiServer)` registers `@fastify/websocket` and the socket routes |
|
|
176
|
+
| `createSocketMiddleware(web = 'api-server', socket = DEFAULT_ALIAS)` | function | Loading-stage context middleware that attaches the socket service to the API server once |
|
|
177
|
+
| `DEFAULT_ALIAS` | const | `'socket-server'` |
|
|
178
|
+
| `SocketService` | type | `InitializedService` with `update(api: ApiServer)` |
|
|
179
|
+
| `Config`, `Context` | type | `ServerConfig` and `ServerContext & ApiServerAppend` |
|
|
180
|
+
|
|
181
|
+
## Common pitfalls
|
|
182
|
+
|
|
183
|
+
- Declare the route with `socket()`; without it the declaration is mounted as a plain HTTP `GET`.
|
|
184
|
+
- Bind with the same protocol object on both sides: `bind(p, connection(p, callback))`.
|
|
185
|
+
- Guards and gates are inherited from the protocol tree and enforced before the handler runs; do not
|
|
186
|
+
re-implement them inside the callback.
|
|
187
|
+
- A socket that authenticates on its own must call `attachEntity(context, request)`, or
|
|
188
|
+
`request.entity` stays empty and entity-keyed lookups silently find nothing.
|
|
189
|
+
- The token query parameter is used only to stamp `sender` / `recipient` on frames. It is not
|
|
190
|
+
verified — never treat it as authentication.
|
|
191
|
+
- Release subscriptions and watchers on the System `close` frame. Listeners also receive heartbeat
|
|
192
|
+
frames, so check `type` and `event` before acting.
|
|
193
|
+
- Do not expose Fastify request or socket objects through a shared contract.
|
|
194
|
+
|
|
195
|
+
## Related packages
|
|
196
|
+
|
|
197
|
+
- [`@owlmeans/socket`](../socket) — `Connection`, `MessageType`, `EventMessage`
|
|
198
|
+
- [`@owlmeans/client-socket`](../client-socket) — the browser and native carrier
|
|
199
|
+
- [`@owlmeans/server-api`](../server-api) — the API server this service attaches to
|
|
200
|
+
- [`@owlmeans/server-entrypoint`](../server-entrypoint) — `bind()`
|
|
65
201
|
- [`@owlmeans/server-app`](../server-app) — calls `appendSocketService` in `makeContext`
|
|
66
|
-
- [`@owlmeans/
|
|
202
|
+
- [`@owlmeans/auth-common`](../auth-common) — `attachEntity`, `requireEntityKey`, `DEFAULT_GUARD`
|
|
67
203
|
|
|
68
204
|
<!-- owlmeans:agent-guidance:start -->
|
|
69
205
|
## Agent guidance
|
|
@@ -73,7 +209,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
73
209
|
your project's skill store (`.agents/skills/`):
|
|
74
210
|
|
|
75
211
|
```sh
|
|
76
|
-
npx @owlmeans/agent-skills
|
|
212
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.45
|
|
77
213
|
```
|
|
78
214
|
|
|
79
215
|
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-
|
|
4
|
+
"version": "0.1.18-rc.50",
|
|
5
|
+
"generatedAt": "2026-10-01T23:27:48.745Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -1,32 +1,29 @@
|
|
|
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
|
-
**Install:** `"@owlmeans/server-socket": "^0.1.18-rc.0"` in `dependencies`
|
|
9
|
+
**Install:** `bun add @owlmeans/server-socket@^0.1.18-rc.50`
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
Declare a socket route and its contract in the shared protocol package. Bind it on the server with
|
|
12
|
+
`connection()` and `bind()`.
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
| Middleware | Socket middleware for Fastify |
|
|
19
|
-
| Helpers | Channel subscription / broadcast helpers |
|
|
20
|
-
| Constants | Default channel names |
|
|
14
|
+
```ts
|
|
15
|
+
import { bind } from '@owlmeans/server-entrypoint'
|
|
16
|
+
import { connection } from '@owlmeans/server-socket'
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
export const serverBindings = [
|
|
19
|
+
bind(streamProtocols.watch, connection(streamProtocols.watch,
|
|
20
|
+
async (connection, context, request) => {
|
|
21
|
+
await connection.send({ type: 'ready' })
|
|
22
|
+
}
|
|
23
|
+
)),
|
|
24
|
+
]
|
|
27
25
|
```
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
- `@fastify/websocket` (runtime)
|
|
27
|
+
The handler receives a typed connection, context, request and response boundary. Guards and gates
|
|
28
|
+
are inherited from the protocol tree and enforced before the WebSocket handler runs. Do not expose
|
|
29
|
+
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/build/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAW,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAW,MAAM,sBAAsB,CAAA;AAapE,eAAO,MAAM,mBAAmB,WAAW,MAAM,KAAmB,aAyGnE,CAAA;AAED,eAAO,MAAM,mBAAmB,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,OACnE,CAAC,UAAS,MAAM,KACpB,CAOF,CAAA"}
|
package/build/service.js
CHANGED
|
@@ -2,7 +2,7 @@ import { assertContext, createService } from '@owlmeans/context';
|
|
|
2
2
|
import { DEFAULT_ALIAS } from './consts.js';
|
|
3
3
|
import { canServerModule } from './utils/server.js';
|
|
4
4
|
import { fastifyWebsocket } from '@fastify/websocket';
|
|
5
|
-
import { authorize, executeResponse, extractContext, handleError, populateContext, provideRequest } from '@owlmeans/server-api/utils';
|
|
5
|
+
import { authorize, errorExposure, executeResponse, extractContext, handleError, populateContext, provideRequest } from '@owlmeans/server-api/utils';
|
|
6
6
|
import { EntrypointOutcome, provideResponse } from '@owlmeans/entrypoint';
|
|
7
7
|
import { ResilientError } from '@owlmeans/error';
|
|
8
8
|
export const createSocketService = (alias = DEFAULT_ALIAS) => {
|
|
@@ -20,8 +20,7 @@ export const createSocketService = (alias = DEFAULT_ALIAS) => {
|
|
|
20
20
|
.filter(module => canServerModule(context, module) && !module.route.isIntermediate())
|
|
21
21
|
.reduce(async (ctx, module) => {
|
|
22
22
|
let context = await ctx;
|
|
23
|
-
|
|
24
|
-
if (!module.route.match(req)) {
|
|
23
|
+
if (!module.route.match(req, module.mount())) {
|
|
25
24
|
return context;
|
|
26
25
|
}
|
|
27
26
|
if (reply.sent) {
|
|
@@ -43,24 +42,22 @@ export const createSocketService = (alias = DEFAULT_ALIAS) => {
|
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
44
|
catch (error) {
|
|
46
|
-
console.error(error);
|
|
47
45
|
if (module.fixer != null) {
|
|
48
46
|
const fixer = context.service(module.fixer);
|
|
49
47
|
fixer.handle(reply, ResilientError.ensure(error));
|
|
50
48
|
}
|
|
51
49
|
else {
|
|
52
|
-
handleError(error, reply);
|
|
50
|
+
handleError(error, reply, errorExposure(context.cfg));
|
|
53
51
|
}
|
|
54
52
|
}
|
|
55
53
|
return context;
|
|
56
54
|
}, Promise.resolve(context));
|
|
57
55
|
});
|
|
58
|
-
|
|
59
|
-
await module.resolve();
|
|
56
|
+
ctx.entrypoints().filter(module => canServerModule(ctx, module) && !module.route.isIntermediate()).forEach(module => {
|
|
60
57
|
if (module.handle == null) {
|
|
61
58
|
return;
|
|
62
59
|
}
|
|
63
|
-
server.get(module.
|
|
60
|
+
server.get(module.mount(), {
|
|
64
61
|
schema: {
|
|
65
62
|
querystring: module.filter?.query ?? {},
|
|
66
63
|
params: module.filter?.params ?? {},
|
|
@@ -92,7 +89,7 @@ export const createSocketService = (alias = DEFAULT_ALIAS) => {
|
|
|
92
89
|
}
|
|
93
90
|
});
|
|
94
91
|
});
|
|
95
|
-
})
|
|
92
|
+
});
|
|
96
93
|
});
|
|
97
94
|
}
|
|
98
95
|
}, service => async () => {
|
package/build/service.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAGhE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAErD,OAAO,
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/utils/connection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,kBAAkB,CAAA;AAChE,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAI3D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAOtD,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,WACvE,eAAe,CAAC,SAAS,CAAC,WAAW,CAAC,KAC9C,
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/utils/connection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,kBAAkB,CAAA;AAChE,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAI3D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAOtD,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,WACvE,eAAe,CAAC,SAAS,CAAC,WAAW,CAAC,KAC9C,UAoKF,CAAA"}
|
|
@@ -6,9 +6,15 @@ export const makeConnection = (request, context) => {
|
|
|
6
6
|
if (request.body == null) {
|
|
7
7
|
throw new SocketInitializationError('request');
|
|
8
8
|
}
|
|
9
|
-
let auth =
|
|
9
|
+
let auth = request.auth;
|
|
10
10
|
const conn = request.body;
|
|
11
11
|
const model = createBasicConnection();
|
|
12
|
+
model.requiresAuthentication = true;
|
|
13
|
+
if (request.auth != null) {
|
|
14
|
+
// The HTTP guard already authenticated this upgrade. In-band authentication remains required
|
|
15
|
+
// for unguarded socket routes before any call, request, event, or message can be dispatched.
|
|
16
|
+
model.stage = AuthenticationStage.Authenticated;
|
|
17
|
+
}
|
|
12
18
|
model.close = async () => {
|
|
13
19
|
await conn.close();
|
|
14
20
|
// @TODO check if on close event is triggered
|
|
@@ -79,26 +85,33 @@ export const makeConnection = (request, context) => {
|
|
|
79
85
|
}
|
|
80
86
|
return message;
|
|
81
87
|
};
|
|
82
|
-
const
|
|
88
|
+
const receiveMessage = async (_message) => {
|
|
83
89
|
_message = Array.isArray(_message) ? _message : [_message];
|
|
84
90
|
const message = _message.map(msg => msg.toString('utf8')).join('');
|
|
85
91
|
if (message.startsWith('{') || message.startsWith('[')) {
|
|
86
92
|
try {
|
|
87
93
|
const parsed = JSON.parse(message);
|
|
88
94
|
if (parsed.type === 'ping') {
|
|
89
|
-
console.log("Received soft ping, sending pong.");
|
|
90
95
|
conn.pong();
|
|
91
96
|
conn.send(JSON.stringify({ type: 'pong' }));
|
|
97
|
+
return;
|
|
92
98
|
}
|
|
93
99
|
}
|
|
94
|
-
catch
|
|
95
|
-
|
|
100
|
+
catch {
|
|
101
|
+
// The shared parser below returns the typed malformed-frame error.
|
|
96
102
|
}
|
|
97
103
|
}
|
|
98
104
|
await model.receive(message);
|
|
99
105
|
};
|
|
106
|
+
// EventEmitter does not consume a returned promise. Keep the registered callback synchronous
|
|
107
|
+
// and terminate only this socket when parsing, staging, or dispatch rejects.
|
|
108
|
+
const messageHandler = (_message) => {
|
|
109
|
+
void receiveMessage(_message).catch(error => {
|
|
110
|
+
console.error('WebSocket message rejected:', error);
|
|
111
|
+
conn.close(1008);
|
|
112
|
+
});
|
|
113
|
+
};
|
|
100
114
|
conn.on("ping", () => {
|
|
101
|
-
console.log("Received hard ping, sending pong.");
|
|
102
115
|
conn.pong();
|
|
103
116
|
});
|
|
104
117
|
conn.on('open', (ws) => {
|
|
@@ -111,7 +124,7 @@ export const makeConnection = (request, context) => {
|
|
|
111
124
|
clearInterval(heartbeat);
|
|
112
125
|
});
|
|
113
126
|
});
|
|
114
|
-
const
|
|
127
|
+
const handleClose = async (code) => {
|
|
115
128
|
const msg = {
|
|
116
129
|
type: MessageType.System,
|
|
117
130
|
event: 'close',
|
|
@@ -120,10 +133,22 @@ export const makeConnection = (request, context) => {
|
|
|
120
133
|
if (model.prepare != null) {
|
|
121
134
|
model.prepare(msg);
|
|
122
135
|
}
|
|
123
|
-
await Promise.all(model.getListeners().map(async (listener) =>
|
|
136
|
+
await Promise.all(model.getListeners().map(async (listener) => {
|
|
137
|
+
try {
|
|
138
|
+
await listener(msg);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
console.error('Socket close listener error:', error);
|
|
142
|
+
}
|
|
143
|
+
}));
|
|
124
144
|
conn.off('message', messageHandler);
|
|
125
145
|
conn.off('close', closeHandler);
|
|
126
146
|
};
|
|
147
|
+
const closeHandler = (code) => {
|
|
148
|
+
void handleClose(code).catch(error => {
|
|
149
|
+
console.error('WebSocket close handling failed:', error);
|
|
150
|
+
});
|
|
151
|
+
};
|
|
127
152
|
conn.on('message', messageHandler);
|
|
128
153
|
conn.on('close', closeHandler);
|
|
129
154
|
return model;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/utils/connection.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,iBAAiB,EAAE,qBAAqB,EAAE,WAAW,EAAE,yBAAyB,EAAE,kBAAkB,EACrG,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAEhE,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACvE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAG1E,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,OAAmC,EAAE,OAAU,EACnC,EAAE;IACd,IAAI,OAAO,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;QACzB,MAAM,IAAI,yBAAyB,CAAC,SAAS,CAAC,CAAA;IAChD,CAAC;IACD,IAAI,IAAI,GAA8C,
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/utils/connection.ts"],"names":[],"mappings":"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"}
|
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.50",
|
|
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.37",
|
|
35
|
+
"@owlmeans/basic-envelope": "^0.1.18-rc.42",
|
|
36
|
+
"@owlmeans/context": "^0.1.18-rc.32",
|
|
37
|
+
"@owlmeans/entrypoint": "^0.1.18-rc.38",
|
|
38
|
+
"@owlmeans/server-api": "^0.1.18-rc.47",
|
|
39
|
+
"@owlmeans/server-auth": "^0.1.18-rc.50",
|
|
40
|
+
"@owlmeans/server-context": "^0.1.18-rc.39",
|
|
41
|
+
"@owlmeans/server-entrypoint": "^0.1.18-rc.37",
|
|
42
|
+
"@owlmeans/socket": "^0.1.18-rc.36"
|
|
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) => {
|
package/src/service.ts
CHANGED
|
@@ -6,7 +6,9 @@ import type { FixerService, ServerEntrypoint } from '@owlmeans/server-entrypoint
|
|
|
6
6
|
import { canServerModule } from './utils/server.js'
|
|
7
7
|
import { fastifyWebsocket } from '@fastify/websocket'
|
|
8
8
|
import type { WebSocket } from '@fastify/websocket'
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
authorize, errorExposure, executeResponse, extractContext, handleError, populateContext, provideRequest
|
|
11
|
+
} from '@owlmeans/server-api/utils'
|
|
10
12
|
import { EntrypointOutcome, provideResponse } from '@owlmeans/entrypoint'
|
|
11
13
|
import type { AbstractRequest, GateService } from '@owlmeans/entrypoint'
|
|
12
14
|
import { ResilientError } from '@owlmeans/error'
|
|
@@ -27,9 +29,8 @@ export const createSocketService = (alias: string = DEFAULT_ALIAS): SocketServic
|
|
|
27
29
|
.filter(module => canServerModule(context, module) && !module.route.isIntermediate())
|
|
28
30
|
.reduce<Promise<Context>>(async (ctx, module) => {
|
|
29
31
|
let context = await ctx
|
|
30
|
-
await module.resolve()
|
|
31
32
|
|
|
32
|
-
if (!module.route.match(req)) {
|
|
33
|
+
if (!module.route.match(req, module.mount())) {
|
|
33
34
|
return context
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -55,12 +56,11 @@ export const createSocketService = (alias: string = DEFAULT_ALIAS): SocketServic
|
|
|
55
56
|
executeResponse(response, reply, true)
|
|
56
57
|
}
|
|
57
58
|
} catch (error) {
|
|
58
|
-
console.error(error)
|
|
59
59
|
if (module.fixer != null) {
|
|
60
60
|
const fixer: FixerService = context.service(module.fixer)
|
|
61
61
|
fixer.handle(reply, ResilientError.ensure(error as Error))
|
|
62
62
|
} else {
|
|
63
|
-
handleError(error as Error, reply)
|
|
63
|
+
handleError(error as Error, reply, errorExposure(context.cfg))
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
@@ -68,15 +68,14 @@ export const createSocketService = (alias: string = DEFAULT_ALIAS): SocketServic
|
|
|
68
68
|
}, Promise.resolve(context))
|
|
69
69
|
})
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
ctx.entrypoints<ServerEntrypoint<Request>>().filter(
|
|
72
72
|
module => canServerModule(ctx, module) && !module.route.isIntermediate()
|
|
73
|
-
).
|
|
74
|
-
await module.resolve()
|
|
73
|
+
).forEach(module => {
|
|
75
74
|
if (module.handle == null) {
|
|
76
75
|
return
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
server.get(module.
|
|
78
|
+
server.get(module.mount(), {
|
|
80
79
|
schema: {
|
|
81
80
|
querystring: module.filter?.query ?? {},
|
|
82
81
|
params: module.filter?.params ?? {},
|
|
@@ -111,7 +110,7 @@ export const createSocketService = (alias: string = DEFAULT_ALIAS): SocketServic
|
|
|
111
110
|
}
|
|
112
111
|
})
|
|
113
112
|
})
|
|
114
|
-
})
|
|
113
|
+
})
|
|
115
114
|
})
|
|
116
115
|
}
|
|
117
116
|
}, service => async () => {
|
package/src/utils/connection.ts
CHANGED
|
@@ -17,10 +17,16 @@ export const makeConnection = <C extends Config, T extends Context<C> = Context<
|
|
|
17
17
|
if (request.body == null) {
|
|
18
18
|
throw new SocketInitializationError('request')
|
|
19
19
|
}
|
|
20
|
-
let auth: Auth | AuthCredentials | null | undefined =
|
|
20
|
+
let auth: Auth | AuthCredentials | null | undefined = request.auth
|
|
21
21
|
const conn = request.body
|
|
22
22
|
|
|
23
23
|
const model = createBasicConnection()
|
|
24
|
+
model.requiresAuthentication = true
|
|
25
|
+
if (request.auth != null) {
|
|
26
|
+
// The HTTP guard already authenticated this upgrade. In-band authentication remains required
|
|
27
|
+
// for unguarded socket routes before any call, request, event, or message can be dispatched.
|
|
28
|
+
model.stage = AuthenticationStage.Authenticated
|
|
29
|
+
}
|
|
24
30
|
|
|
25
31
|
model.close = async () => {
|
|
26
32
|
await conn.close()
|
|
@@ -97,7 +103,7 @@ export const makeConnection = <C extends Config, T extends Context<C> = Context<
|
|
|
97
103
|
return message
|
|
98
104
|
}
|
|
99
105
|
|
|
100
|
-
const
|
|
106
|
+
const receiveMessage = async (_message: Buffer | Buffer[]) => {
|
|
101
107
|
_message = Array.isArray(_message) ? _message : [_message]
|
|
102
108
|
const message = _message.map(msg => msg.toString('utf8')).join('')
|
|
103
109
|
|
|
@@ -105,20 +111,28 @@ export const makeConnection = <C extends Config, T extends Context<C> = Context<
|
|
|
105
111
|
try {
|
|
106
112
|
const parsed = JSON.parse(message)
|
|
107
113
|
if (parsed.type === 'ping') {
|
|
108
|
-
console.log("Received soft ping, sending pong.")
|
|
109
114
|
conn.pong()
|
|
110
115
|
conn.send(JSON.stringify({ type: 'pong' }))
|
|
116
|
+
return
|
|
111
117
|
}
|
|
112
|
-
} catch
|
|
113
|
-
|
|
118
|
+
} catch {
|
|
119
|
+
// The shared parser below returns the typed malformed-frame error.
|
|
114
120
|
}
|
|
115
121
|
}
|
|
116
122
|
|
|
117
123
|
await model.receive(message)
|
|
118
124
|
}
|
|
119
125
|
|
|
126
|
+
// EventEmitter does not consume a returned promise. Keep the registered callback synchronous
|
|
127
|
+
// and terminate only this socket when parsing, staging, or dispatch rejects.
|
|
128
|
+
const messageHandler = (_message: Buffer | Buffer[]) => {
|
|
129
|
+
void receiveMessage(_message).catch(error => {
|
|
130
|
+
console.error('WebSocket message rejected:', error)
|
|
131
|
+
conn.close(1008)
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
120
135
|
conn.on("ping", () => {
|
|
121
|
-
console.log("Received hard ping, sending pong.")
|
|
122
136
|
conn.pong()
|
|
123
137
|
})
|
|
124
138
|
|
|
@@ -133,7 +147,7 @@ export const makeConnection = <C extends Config, T extends Context<C> = Context<
|
|
|
133
147
|
})
|
|
134
148
|
})
|
|
135
149
|
|
|
136
|
-
const
|
|
150
|
+
const handleClose = async (code: number) => {
|
|
137
151
|
const msg: EventMessage<{ code: number }> = {
|
|
138
152
|
type: MessageType.System,
|
|
139
153
|
event: 'close',
|
|
@@ -142,11 +156,23 @@ export const makeConnection = <C extends Config, T extends Context<C> = Context<
|
|
|
142
156
|
if (model.prepare != null) {
|
|
143
157
|
model.prepare(msg)
|
|
144
158
|
}
|
|
145
|
-
await Promise.all(model.getListeners().map(async listener =>
|
|
159
|
+
await Promise.all(model.getListeners().map(async listener => {
|
|
160
|
+
try {
|
|
161
|
+
await listener(msg)
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error('Socket close listener error:', error)
|
|
164
|
+
}
|
|
165
|
+
}))
|
|
146
166
|
conn.off('message', messageHandler)
|
|
147
167
|
conn.off('close', closeHandler)
|
|
148
168
|
}
|
|
149
169
|
|
|
170
|
+
const closeHandler = (code: number) => {
|
|
171
|
+
void handleClose(code).catch(error => {
|
|
172
|
+
console.error('WebSocket close handling failed:', error)
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
|
|
150
176
|
conn.on('message', messageHandler)
|
|
151
177
|
conn.on('close', closeHandler)
|
|
152
178
|
|
package/build/.gitkeep
DELETED
|
File without changes
|