@grest-ts/websocket 0.0.23 → 0.0.24
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 +150 -40
- package/dist/src/adapter/NodeSocketAdapter.d.ts +2 -0
- package/dist/src/adapter/NodeSocketAdapter.d.ts.map +1 -1
- package/dist/src/adapter/NodeSocketAdapter.js +6 -0
- package/dist/src/adapter/NodeSocketAdapter.js.map +1 -1
- package/dist/src/client/GGSocketPool.d.ts +21 -0
- package/dist/src/client/GGSocketPool.d.ts.map +1 -1
- package/dist/src/client/GGSocketPool.js +82 -63
- package/dist/src/client/GGSocketPool.js.map +1 -1
- package/dist/src/client/GGWebSocketSchema.createClient.d.ts +154 -0
- package/dist/src/client/GGWebSocketSchema.createClient.d.ts.map +1 -0
- package/dist/src/client/GGWebSocketSchema.createClient.js +345 -0
- package/dist/src/client/GGWebSocketSchema.createClient.js.map +1 -0
- package/dist/src/index-browser.d.ts +2 -1
- package/dist/src/index-browser.d.ts.map +1 -1
- package/dist/src/index-browser.js +3 -1
- package/dist/src/index-browser.js.map +1 -1
- package/dist/src/index-node.d.ts +2 -1
- package/dist/src/index-node.d.ts.map +1 -1
- package/dist/src/index-node.js +2 -1
- package/dist/src/index-node.js.map +1 -1
- package/dist/src/schema/GGWebSocketMiddleware.d.ts +12 -0
- package/dist/src/schema/GGWebSocketMiddleware.d.ts.map +1 -1
- package/dist/src/schema/GGWebSocketMiddleware.js +0 -4
- package/dist/src/schema/GGWebSocketMiddleware.js.map +1 -1
- package/dist/src/schema/GGWebSocketSchema.d.ts +4 -3
- package/dist/src/schema/GGWebSocketSchema.d.ts.map +1 -1
- package/dist/src/schema/GGWebSocketSchema.js +3 -1
- package/dist/src/schema/GGWebSocketSchema.js.map +1 -1
- package/dist/src/schema/webSocketSchema.d.ts +12 -6
- package/dist/src/schema/webSocketSchema.d.ts.map +1 -1
- package/dist/src/schema/webSocketSchema.js +9 -2
- package/dist/src/schema/webSocketSchema.js.map +1 -1
- package/dist/src/server/GGSocketServer.d.ts.map +1 -1
- package/dist/src/server/GGSocketServer.js +36 -2
- package/dist/src/server/GGSocketServer.js.map +1 -1
- package/dist/src/server/GGWebSocketSchema.startServer.d.ts +5 -3
- package/dist/src/server/GGWebSocketSchema.startServer.d.ts.map +1 -1
- package/dist/src/server/GGWebSocketSchema.startServer.js +7 -5
- package/dist/src/server/GGWebSocketSchema.startServer.js.map +1 -1
- package/dist/src/socket/GGSocket.d.ts +13 -1
- package/dist/src/socket/GGSocket.d.ts.map +1 -1
- package/dist/src/socket/GGSocket.js +52 -2
- package/dist/src/socket/GGSocket.js.map +1 -1
- package/dist/src/socket/SocketAdapter.d.ts +11 -0
- package/dist/src/socket/SocketAdapter.d.ts.map +1 -1
- package/dist/testkit/client/GGWebSocketSchema.callOn.d.ts +1 -1
- package/dist/testkit/client/GGWebSocketSchema.callOn.d.ts.map +1 -1
- package/dist/tsconfig.publish.tsbuildinfo +1 -1
- package/package.json +11 -11
- package/src/adapter/NodeSocketAdapter.ts +8 -0
- package/src/client/GGSocketPool.ts +90 -73
- package/src/client/GGWebSocketSchema.createClient.ts +534 -0
- package/src/index-browser.ts +5 -2
- package/src/index-node.ts +2 -1
- package/src/schema/GGWebSocketMiddleware.ts +14 -0
- package/src/schema/GGWebSocketSchema.ts +7 -3
- package/src/schema/webSocketSchema.ts +18 -8
- package/src/server/GGSocketServer.ts +51 -2
- package/src/server/GGWebSocketSchema.startServer.ts +14 -10
- package/src/socket/GGSocket.ts +56 -2
- package/src/socket/SocketAdapter.ts +13 -0
- package/dist/src/client/GGSocketClient.d.ts +0 -10
- package/dist/src/client/GGSocketClient.d.ts.map +0 -1
- package/dist/src/client/GGSocketClient.js +0 -17
- package/dist/src/client/GGSocketClient.js.map +0 -1
- package/src/client/GGSocketClient.ts +0 -25
package/README.md
CHANGED
|
@@ -209,6 +209,88 @@ export const ChatApi = webSocketSchema(ChatApiContract)
|
|
|
209
209
|
|
|
210
210
|
Middlewares run in order during connection establishment.
|
|
211
211
|
|
|
212
|
+
### Sharing middleware with HTTP APIs (one class, two transports)
|
|
213
|
+
|
|
214
|
+
Most apps are HTTP-first and add WebSockets later. You'll often want the *same* auth logic on both — same bearer-token shape, same verification, same user context key. The two middleware interfaces are deliberately separate (HTTP runs per-request; WS runs once at handshake — see the note below), but TypeScript structural typing lets **one class implement both interfaces** so you write the logic once and attach it to both schemas.
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import { GGHttpTransportMiddleware, GGHttpRequest } from "@grest-ts/http"
|
|
218
|
+
import { GGWebSocketMiddleware, GGWebSocketHandshakeContext } from "@grest-ts/websocket"
|
|
219
|
+
import { GGContextKey } from "@grest-ts/context"
|
|
220
|
+
import { IsObject, IsString, NOT_AUTHORIZED } from "@grest-ts/schema"
|
|
221
|
+
|
|
222
|
+
export const IsAuthUser = IsObject({ id: IsString, role: IsString })
|
|
223
|
+
export type AuthUser = typeof IsAuthUser.infer
|
|
224
|
+
export const GG_AUTH_USER = new GGContextKey<AuthUser>("authUser", IsAuthUser)
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Implements both middleware interfaces. Use the same instance on HTTP and
|
|
228
|
+
* WebSocket schemas — single source of truth for auth wiring.
|
|
229
|
+
*/
|
|
230
|
+
export class BearerAuthMiddleware
|
|
231
|
+
implements GGHttpTransportMiddleware, GGWebSocketMiddleware {
|
|
232
|
+
|
|
233
|
+
constructor(private opts: {
|
|
234
|
+
/** Client-side: return the current token. Called on every HTTP request AND on every WS handshake. */
|
|
235
|
+
getToken: () => string | undefined
|
|
236
|
+
/** Server-side: verify the token and return the user. Throw/return undefined to reject. */
|
|
237
|
+
verify: (token: string) => AuthUser | undefined
|
|
238
|
+
}) {}
|
|
239
|
+
|
|
240
|
+
// ---- Client-side: attach the bearer header ----
|
|
241
|
+
updateRequest = (req: GGHttpRequest) =>
|
|
242
|
+
this.setHeader(req.headers as Record<string, string>)
|
|
243
|
+
updateHandshake = (ctx: GGWebSocketHandshakeContext) =>
|
|
244
|
+
this.setHeader(ctx.headers)
|
|
245
|
+
|
|
246
|
+
// ---- Server-side: extract + verify, populate context ----
|
|
247
|
+
parseRequest = (req: GGHttpRequest) =>
|
|
248
|
+
this.extract(req.headers as Record<string, string | string[]>)
|
|
249
|
+
parseHandshake = (ctx: GGWebSocketHandshakeContext) =>
|
|
250
|
+
this.extract(ctx.headers)
|
|
251
|
+
|
|
252
|
+
private setHeader(headers: Record<string, string>) {
|
|
253
|
+
const t = this.opts.getToken()
|
|
254
|
+
if (t) headers["authorization"] = "Bearer " + t
|
|
255
|
+
}
|
|
256
|
+
private extract(headers: Record<string, string | string[]>) {
|
|
257
|
+
const header = headers["authorization"]
|
|
258
|
+
if (typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
259
|
+
throw new NOT_AUTHORIZED({ displayMessage: "Missing bearer token" })
|
|
260
|
+
}
|
|
261
|
+
const user = this.opts.verify(header.substring(7))
|
|
262
|
+
if (!user) throw new NOT_AUTHORIZED({ displayMessage: "Invalid token" })
|
|
263
|
+
GG_AUTH_USER.set(user)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// One instance, used on both kinds of schema:
|
|
268
|
+
const auth = new BearerAuthMiddleware({
|
|
269
|
+
getToken: () => GG_AUTH_USER.get()?.id,
|
|
270
|
+
verify: (token) => validateTokenSync(token),
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
export const ItemApi = httpSchema(ItemContract).pathPrefix("api/items")
|
|
274
|
+
.use(auth) // acts as GGHttpTransportMiddleware
|
|
275
|
+
.routes({ ... })
|
|
276
|
+
|
|
277
|
+
export const ChatApi = webSocketSchema(ChatContract).path("ws/chat")
|
|
278
|
+
.use(auth) // acts as GGWebSocketMiddleware
|
|
279
|
+
.done()
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Important — the rhythms are different:**
|
|
283
|
+
|
|
284
|
+
| | HTTP | WebSocket |
|
|
285
|
+
|---|---|---|
|
|
286
|
+
| When middleware runs | Per request | Once, at handshake |
|
|
287
|
+
| What it can do | Modify each request/response | Set connection-scoped context |
|
|
288
|
+
| Token refresh | Naturally handled: next request reads the new token | Not automatic — token is captured at connect time. If the token rotates mid-session, the old connection keeps its old identity until it's dropped and a fresh handshake runs |
|
|
289
|
+
|
|
290
|
+
This is why the interfaces aren't merged: forcing a single interface would make WS middleware silently not re-run on messages (a foot-gun). Keep the rhythms distinct and share *logic*, not *lifecycle*.
|
|
291
|
+
|
|
292
|
+
The server-side extraction logic here is identical for both transports — that's the common case and the reason this pattern pays off. If your HTTP flow needs per-request behavior that doesn't map to WS (say, modifying the HTTP response body), put those hooks on a separate HTTP-only middleware and apply both.
|
|
293
|
+
|
|
212
294
|
## Server Setup
|
|
213
295
|
|
|
214
296
|
### Connection Handler
|
|
@@ -314,65 +396,93 @@ protected compose(): void {
|
|
|
314
396
|
|
|
315
397
|
## Client
|
|
316
398
|
|
|
317
|
-
###
|
|
399
|
+
### Typed Client via `createClient()`
|
|
318
400
|
|
|
319
|
-
`
|
|
401
|
+
`ChatApi.createClient()` returns a typed, contract-validated client. It mirrors the server's connection handler: `incoming.on(handlers)` for `serverToClient` messages, `outgoing.method(data)` for `clientToServer` methods.
|
|
320
402
|
|
|
321
403
|
```typescript
|
|
322
|
-
import {
|
|
404
|
+
import { ChatApi } from "./ChatApi"
|
|
323
405
|
|
|
324
|
-
//
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
406
|
+
// Create the client (disconnected)
|
|
407
|
+
const client = ChatApi.createClient({ url: "ws://localhost:3000" })
|
|
408
|
+
|
|
409
|
+
// Register handlers for serverToClient messages — Partial, only what you need
|
|
410
|
+
client.incoming.on({
|
|
411
|
+
newMessage: (message) => {
|
|
412
|
+
console.log("New message:", message)
|
|
413
|
+
},
|
|
414
|
+
typing: (event) => {
|
|
415
|
+
console.log(event.userId, "is typing")
|
|
416
|
+
},
|
|
417
|
+
// Server-requests-client RPC (has `success` in contract) — return a value
|
|
418
|
+
areYouThere: async () => true
|
|
329
419
|
})
|
|
330
420
|
|
|
331
|
-
//
|
|
332
|
-
|
|
421
|
+
// Lifecycle callbacks can be registered before connect
|
|
422
|
+
client.onClose(() => console.log("Disconnected"))
|
|
423
|
+
client.onError((err) => console.error("Socket error:", err))
|
|
424
|
+
|
|
425
|
+
// Establish the connection (runs handshake + applies pending handlers)
|
|
426
|
+
await client.connect()
|
|
427
|
+
|
|
428
|
+
// Call clientToServer methods — returns GGPromise like the HTTP client
|
|
429
|
+
const response = await client.outgoing.sendMessage({
|
|
333
430
|
text: "Hello!",
|
|
334
431
|
channelId: "general"
|
|
335
|
-
}, true)
|
|
336
|
-
|
|
337
|
-
// Register handler for server-to-client messages
|
|
338
|
-
socket.registerHandler({
|
|
339
|
-
path: "ChatApi.newMessage",
|
|
340
|
-
handler: (message) => {
|
|
341
|
-
console.log("New message:", message)
|
|
342
|
-
}
|
|
343
432
|
})
|
|
433
|
+
// response is typed: { success: true, messageId: "msg-456" }
|
|
434
|
+
|
|
435
|
+
// Fire-and-forget methods (no `success` in contract) — returns Promise<void>
|
|
436
|
+
await client.outgoing.markAsRead({ messageId: "msg-123" })
|
|
437
|
+
await client.outgoing.ping()
|
|
438
|
+
|
|
439
|
+
// Error handling — same GGPromise API as the HTTP client
|
|
440
|
+
const result = await client.outgoing.sendMessage({ text: "", channelId: "general" }).asResult()
|
|
441
|
+
if (result.success) {
|
|
442
|
+
console.log(result.data.messageId)
|
|
443
|
+
} else if (result.type === "VALIDATION_ERROR") {
|
|
444
|
+
showValidationErrors(result.data)
|
|
445
|
+
}
|
|
344
446
|
|
|
345
|
-
//
|
|
346
|
-
|
|
347
|
-
|
|
447
|
+
// Gracefully close (waits for pending requests), or close() for immediate termination
|
|
448
|
+
await client.disconnect()
|
|
449
|
+
```
|
|
348
450
|
|
|
349
|
-
|
|
350
|
-
socket.close()
|
|
451
|
+
### Client Config
|
|
351
452
|
|
|
352
|
-
|
|
353
|
-
|
|
453
|
+
```typescript
|
|
454
|
+
interface GGWebSocketClientConfig<TQuery> {
|
|
455
|
+
url?: string // "ws://host:port". If omitted, uses @grest-ts/discovery.
|
|
456
|
+
query?: TQuery // Query params on connect, typed from `.queryOnConnect<T>()`.
|
|
457
|
+
}
|
|
354
458
|
```
|
|
355
459
|
|
|
356
|
-
|
|
460
|
+
Omitting `url` triggers service discovery via `@grest-ts/discovery` (Node only). In browsers, pass an explicit URL (use `""` for same-origin).
|
|
461
|
+
|
|
462
|
+
### Sending Modes (automatic from the contract)
|
|
463
|
+
|
|
464
|
+
- **Request-response** — methods with `success` defined return `GGPromise<Success, Errors>`. The client sends a `REQ` and waits up to 30s for a reply.
|
|
465
|
+
- **Fire-and-forget** — methods without `success` return `GGPromise<void, SERVER_ERROR>`. The client sends a `MSG` and resolves as soon as the message is handed to the socket.
|
|
357
466
|
|
|
358
|
-
|
|
467
|
+
Both apply symmetrically: the server can also send request-response messages via `serverToClient` methods that define `success`.
|
|
468
|
+
|
|
469
|
+
### Direct socket access via `GGSocketPool`
|
|
470
|
+
|
|
471
|
+
If you need to bypass contract validation (e.g. writing a generic proxy, debugging the wire protocol), `GGSocketPool` is still available. Prefer `createClient()` in application code.
|
|
359
472
|
|
|
360
473
|
```typescript
|
|
361
|
-
|
|
362
|
-
// Sends a REQ message, waits for the server to reply (30s timeout)
|
|
363
|
-
const result = await socket.send("ChatApi.sendMessage", {
|
|
364
|
-
text: "Hello!",
|
|
365
|
-
channelId: "general"
|
|
366
|
-
}, true)
|
|
367
|
-
// result is the typed response: { success: true, messageId: "msg-456" }
|
|
474
|
+
import { GGSocketPool } from "@grest-ts/websocket"
|
|
368
475
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
476
|
+
const socket = await GGSocketPool.getOrConnect({
|
|
477
|
+
domain: "ws://localhost:3000",
|
|
478
|
+
path: "/ws/chat",
|
|
479
|
+
middlewares: ChatApi.middlewares
|
|
480
|
+
})
|
|
374
481
|
|
|
375
|
-
|
|
482
|
+
const result = await socket.send("ChatApi.sendMessage", { text: "Hello!", channelId: "general" }, true)
|
|
483
|
+
socket.registerHandler({ path: "ChatApi.newMessage", handler: (msg) => { ... } })
|
|
484
|
+
socket.close()
|
|
485
|
+
```
|
|
376
486
|
|
|
377
487
|
### Connection Pool Management
|
|
378
488
|
|
|
@@ -17,5 +17,7 @@ export declare class NodeSocketAdapter implements SocketAdapter {
|
|
|
17
17
|
offMessage(handler: (data: string) => void): void;
|
|
18
18
|
offClose(handler: () => void): void;
|
|
19
19
|
offError(handler: (error: Error) => void): void;
|
|
20
|
+
ping(): void;
|
|
21
|
+
onPong(handler: () => void): void;
|
|
20
22
|
}
|
|
21
23
|
//# sourceMappingURL=NodeSocketAdapter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeSocketAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapter/NodeSocketAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,EAAC,aAAa,EAAC,MAAM,yBAAyB,CAAC;AAEtD,MAAM,WAAW,wBAAwB;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,qBAAa,iBAAkB,YAAW,aAAa;IACnD,OAAO,CAAC,EAAE,CAAY;IACtB,OAAO,CAAC,eAAe,CAAuE;gBAElF,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,CAAC,EAAE,wBAAwB;IAU/E,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3B,KAAK,IAAI,IAAI;IAIb,MAAM,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI;IAIjC,SAAS,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAMhD,OAAO,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI;IAIlC,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI;IAI9C,OAAO,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI;IAIlC,UAAU,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAQjD,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI;IAInC,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"NodeSocketAdapter.d.ts","sourceRoot":"","sources":["../../../src/adapter/NodeSocketAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,EAAC,aAAa,EAAC,MAAM,yBAAyB,CAAC;AAEtD,MAAM,WAAW,wBAAwB;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,qBAAa,iBAAkB,YAAW,aAAa;IACnD,OAAO,CAAC,EAAE,CAAY;IACtB,OAAO,CAAC,eAAe,CAAuE;gBAElF,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,CAAC,EAAE,wBAAwB;IAU/E,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3B,KAAK,IAAI,IAAI;IAIb,MAAM,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI;IAIjC,SAAS,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAMhD,OAAO,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI;IAIlC,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI;IAI9C,OAAO,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI;IAIlC,UAAU,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAQjD,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI;IAInC,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI;IAI/C,IAAI,IAAI,IAAI;IAIZ,MAAM,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI;CAGpC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeSocketAdapter.js","sourceRoot":"","sources":["../../../src/adapter/NodeSocketAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,IAAI,CAAC;AAO3B,MAAM,OAAO,iBAAiB;IAClB,EAAE,CAAY;IACd,eAAe,GAAG,IAAI,OAAO,EAAwD,CAAC;IAE9F,YAAY,WAA+B,EAAE,OAAkC;QAC3E,IAAI,OAAO,WAAW,KAAK,QAAQ,EAAE,CAAC;YAClC,IAAI,CAAC,EAAE,GAAG,IAAI,SAAS,CAAC,WAAW,EAAE;gBACjC,OAAO,EAAE,OAAO,EAAE,OAAO;aAC5B,CAAC,CAAC;QACP,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,EAAE,GAAG,WAAW,CAAC;QAC1B,CAAC;IACL,CAAC;IAED,IAAI,CAAC,OAAe;QAChB,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC1B,CAAC;IAED,KAAK;QACD,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;IACpB,CAAC;IAED,MAAM,CAAC,OAAmB;QACtB,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,CAAC;IAED,SAAS,CAAC,OAA+B;QACrC,MAAM,OAAO,GAAG,CAAC,UAAkB,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAA;QACnE,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC3C,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACnC,CAAC;IAED,OAAO,CAAC,OAAmB;QACvB,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACjC,CAAC;IAED,OAAO,CAAC,OAA+B;QACnC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACjC,CAAC;IAED,OAAO,CAAC,OAAmB;QACvB,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,CAAC;IAED,UAAU,CAAC,OAA+B;QACtC,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAClD,IAAI,OAAO,EAAE,CAAC;YACV,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YAChC,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACzC,CAAC;IACL,CAAC;IAED,QAAQ,CAAC,OAAmB;QACxB,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAClC,CAAC;IAED,QAAQ,CAAC,OAA+B;QACpC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAClC,CAAC;CACJ"}
|
|
1
|
+
{"version":3,"file":"NodeSocketAdapter.js","sourceRoot":"","sources":["../../../src/adapter/NodeSocketAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,IAAI,CAAC;AAO3B,MAAM,OAAO,iBAAiB;IAClB,EAAE,CAAY;IACd,eAAe,GAAG,IAAI,OAAO,EAAwD,CAAC;IAE9F,YAAY,WAA+B,EAAE,OAAkC;QAC3E,IAAI,OAAO,WAAW,KAAK,QAAQ,EAAE,CAAC;YAClC,IAAI,CAAC,EAAE,GAAG,IAAI,SAAS,CAAC,WAAW,EAAE;gBACjC,OAAO,EAAE,OAAO,EAAE,OAAO;aAC5B,CAAC,CAAC;QACP,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,EAAE,GAAG,WAAW,CAAC;QAC1B,CAAC;IACL,CAAC;IAED,IAAI,CAAC,OAAe;QAChB,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC1B,CAAC;IAED,KAAK;QACD,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;IACpB,CAAC;IAED,MAAM,CAAC,OAAmB;QACtB,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,CAAC;IAED,SAAS,CAAC,OAA+B;QACrC,MAAM,OAAO,GAAG,CAAC,UAAkB,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAA;QACnE,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC3C,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACnC,CAAC;IAED,OAAO,CAAC,OAAmB;QACvB,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACjC,CAAC;IAED,OAAO,CAAC,OAA+B;QACnC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACjC,CAAC;IAED,OAAO,CAAC,OAAmB;QACvB,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,CAAC;IAED,UAAU,CAAC,OAA+B;QACtC,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAClD,IAAI,OAAO,EAAE,CAAC;YACV,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YAChC,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACzC,CAAC;IACL,CAAC;IAED,QAAQ,CAAC,OAAmB;QACxB,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAClC,CAAC;IAED,QAAQ,CAAC,OAA+B;QACpC,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAClC,CAAC;IAED,IAAI;QACA,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;IACnB,CAAC;IAED,MAAM,CAAC,OAAmB;QACtB,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,CAAC;CACJ"}
|
|
@@ -59,5 +59,26 @@ export declare class GGSocketPool {
|
|
|
59
59
|
*/
|
|
60
60
|
private static buildHeaders;
|
|
61
61
|
static getOrConnect<Query>(config: GGSocketPoolConfig<Query>): Promise<GGSocket>;
|
|
62
|
+
/**
|
|
63
|
+
* Establish a fresh, un-pooled WebSocket connection.
|
|
64
|
+
*
|
|
65
|
+
* Unlike `getOrConnect`, this never reuses or caches connections — every
|
|
66
|
+
* call produces a dedicated socket with its own close lifecycle. Use this
|
|
67
|
+
* when you want each logical client to own its connection (the common
|
|
68
|
+
* case for `createClient()` users).
|
|
69
|
+
*/
|
|
70
|
+
static connect<Query>(config: GGSocketPoolConfig<Query>): Promise<GGSocket>;
|
|
71
|
+
/**
|
|
72
|
+
* Reconstruct the typed error the server threw during handshake.
|
|
73
|
+
*
|
|
74
|
+
* The server sends `error.toJSON()` which has `{success:false, type, data?, context?}`.
|
|
75
|
+
* System errors (NOT_AUTHORIZED, FORBIDDEN, VALIDATION_ERROR, etc.) are reconstructed
|
|
76
|
+
* as real instances so callers can `.toBeError(NOT_AUTHORIZED)`. Anything we can't
|
|
77
|
+
* identify (non-ERROR throw, custom error class the client doesn't know) falls back
|
|
78
|
+
* to SERVER_ERROR carrying the original payload for inspection.
|
|
79
|
+
*/
|
|
80
|
+
private static handshakeErrorFrom;
|
|
81
|
+
private static buildUrl;
|
|
82
|
+
private static openSocket;
|
|
62
83
|
}
|
|
63
84
|
//# sourceMappingURL=GGSocketPool.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GGSocketPool.d.ts","sourceRoot":"","sources":["../../../src/client/GGSocketPool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAA8B,qBAAqB,EAAC,MAAM,iCAAiC,CAAC;AACnG,OAAO,EAAC,aAAa,EAAC,MAAM,yBAAyB,CAAC;AAGtD,OAAO,
|
|
1
|
+
{"version":3,"file":"GGSocketPool.d.ts","sourceRoot":"","sources":["../../../src/client/GGSocketPool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAA8B,qBAAqB,EAAC,MAAM,iCAAiC,CAAC;AACnG,OAAO,EAAC,aAAa,EAAC,MAAM,yBAAyB,CAAC;AAGtD,OAAO,EAAqB,WAAW,EAAe,MAAM,kBAAkB,CAAC;AAM/E,MAAM,WAAW,kBAAkB,CAAC,KAAK;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,cAAc,CAAC,EAAE,WAAW,CAAC,KAAK,CAAC,CAAA;IACnC,WAAW,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAA;CACjD;AAED;;;GAGG;AACH,qBAAa,YAAY;IACrB,OAAO,CAAC,MAAM,CAAC,OAAO,CAA+B;IACrD,OAAO,CAAC,MAAM,CAAC,cAAc,CAAwC;IACrE,OAAO,CAAC,MAAM,CAAC,OAAO,CAAa;IACnC,OAAO,CAAC,MAAM,CAAC,cAAc,CAA6B;IAE1D;;OAEG;IACH,WAAkB,IAAI,IAAI,MAAM,CAE/B;IAED;;OAEG;IACH,WAAkB,WAAW,IAAI,MAAM,CAEtC;IAED;;;;;OAKG;WACiB,QAAQ,CAAC,QAAQ,GAAE,OAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBrE;;;;OAIG;WACW,iBAAiB,IAAI,IAAI;IAOvC;;;;OAIG;WACW,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIlD;;OAEG;WACW,iBAAiB,IAAI,MAAM,EAAE;IAI3C;;OAEG;mBACkB,aAAa;WAcpB,UAAU,CAAC,OAAO,EAAE,KAAI,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,aAAa;IAKhF;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,YAAY;WAiBd,YAAY,CAAC,KAAK,EAC3B,MAAM,EAAE,kBAAkB,CAAC,KAAK,CAAC,GAClC,OAAO,CAAC,QAAQ,CAAC;IAgCpB;;;;;;;OAOG;WACU,OAAO,CAAC,KAAK,EACtB,MAAM,EAAE,kBAAkB,CAAC,KAAK,CAAC,GAClC,OAAO,CAAC,QAAQ,CAAC;IAIpB;;;;;;;;OAQG;IACH,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAUjC,OAAO,CAAC,MAAM,CAAC,QAAQ;mBASF,UAAU;CA4ClC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { GGSocket } from '../socket/GGSocket.js';
|
|
2
2
|
import { GG_WS_CONNECTION } from "../server/GG_WS_CONNECTION.js";
|
|
3
3
|
import { Message, MessageType } from "../socket/SocketMessage.js";
|
|
4
|
-
import { SERVER_ERROR } from "@grest-ts/schema";
|
|
4
|
+
import { GGContractExecutor, SERVER_ERROR } from "@grest-ts/schema";
|
|
5
5
|
import { withTimeout } from "@grest-ts/common";
|
|
6
6
|
import { GGContext } from "@grest-ts/context";
|
|
7
7
|
import { GG_TRACE } from "@grest-ts/trace";
|
|
@@ -112,92 +112,111 @@ export class GGSocketPool {
|
|
|
112
112
|
return handshakeContext.headers;
|
|
113
113
|
}
|
|
114
114
|
static async getOrConnect(config) {
|
|
115
|
-
// Build headers from middlewares
|
|
116
115
|
const headers = this.buildHeaders(config);
|
|
117
|
-
|
|
118
|
-
let fullUrl = config.domain + config.path;
|
|
119
|
-
if (config.query) {
|
|
120
|
-
const queryEntries = Object.entries(config.query).map(([key, value]) => [key, String(value)]);
|
|
121
|
-
fullUrl += '?' + new URLSearchParams(queryEntries).toString();
|
|
122
|
-
}
|
|
116
|
+
const fullUrl = this.buildUrl(config);
|
|
123
117
|
// Create connection key based on URL + headers
|
|
124
118
|
const headerKey = Object.entries(headers).sort().map(([k, v]) => `${k}=${v}`).join('&');
|
|
125
119
|
const key = fullUrl + "::" + headerKey;
|
|
126
|
-
// Check for existing connection first
|
|
127
120
|
if (this.sockets.has(key)) {
|
|
128
121
|
return this.sockets.get(key);
|
|
129
122
|
}
|
|
130
123
|
if (this.pendingSockets.has(key)) {
|
|
131
124
|
return this.pendingSockets.get(key);
|
|
132
125
|
}
|
|
133
|
-
|
|
134
|
-
// This ensures that concurrent calls will see the pending promise
|
|
135
|
-
const connectionPromise = (async () => {
|
|
136
|
-
// Ensure adapter is loaded (this is async but safely inside the promise)
|
|
137
|
-
const adapterClass = await this.ensureAdapter();
|
|
138
|
-
return new Promise((resolve, reject) => {
|
|
139
|
-
const adapter = new adapterClass(fullUrl);
|
|
140
|
-
adapter.onOpen(async () => {
|
|
141
|
-
try {
|
|
142
|
-
const context = new GGContext("ws-client-connection");
|
|
143
|
-
await context.run(async () => {
|
|
144
|
-
GG_TRACE.init();
|
|
145
|
-
GG_WS_CONNECTION.set({
|
|
146
|
-
port: undefined,
|
|
147
|
-
path: config.domain
|
|
148
|
-
});
|
|
149
|
-
// Send handshake with headers
|
|
150
|
-
adapter.send(Message.create(MessageType.HANDSHAKE, "", "", headers));
|
|
151
|
-
// Wait for handshake response
|
|
152
|
-
await withTimeout(new Promise((handshakeResolve, handshakeReject) => {
|
|
153
|
-
const onMessage = (data) => {
|
|
154
|
-
const msg = Message.parse(data);
|
|
155
|
-
if (!msg)
|
|
156
|
-
return;
|
|
157
|
-
if (msg.type === MessageType.HANDSHAKE_OK) {
|
|
158
|
-
adapter.offMessage(onMessage);
|
|
159
|
-
handshakeResolve();
|
|
160
|
-
}
|
|
161
|
-
else if (msg.type === MessageType.HANDSHAKE_ERR) {
|
|
162
|
-
adapter.offMessage(onMessage);
|
|
163
|
-
handshakeReject(new SERVER_ERROR({
|
|
164
|
-
displayMessage: 'WebSocket handshake failed',
|
|
165
|
-
originalError: msg.data
|
|
166
|
-
}));
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
adapter.onMessage(onMessage);
|
|
170
|
-
}), 5000, 'Handshake timeout');
|
|
171
|
-
resolve(new GGSocket(adapter, { connectionContext: context }));
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
catch (error) {
|
|
175
|
-
reject(error);
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
adapter.onError((error) => {
|
|
179
|
-
reject(error);
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
})();
|
|
183
|
-
// Store the pending promise IMMEDIATELY (before awaiting)
|
|
126
|
+
const connectionPromise = this.openSocket(fullUrl, headers, config.domain);
|
|
184
127
|
this.pendingSockets.set(key, connectionPromise);
|
|
185
128
|
try {
|
|
186
129
|
const socket = await connectionPromise;
|
|
187
|
-
// Store the connection
|
|
188
130
|
this.sockets.set(key, socket);
|
|
189
131
|
this.pendingSockets.delete(key);
|
|
190
|
-
// Clean up on close
|
|
191
132
|
socket.onClose(() => {
|
|
192
133
|
this.sockets.delete(key);
|
|
193
134
|
});
|
|
194
135
|
return socket;
|
|
195
136
|
}
|
|
196
137
|
catch (error) {
|
|
197
|
-
// Clean up failed connection attempt
|
|
198
138
|
this.pendingSockets.delete(key);
|
|
199
139
|
throw error;
|
|
200
140
|
}
|
|
201
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Establish a fresh, un-pooled WebSocket connection.
|
|
144
|
+
*
|
|
145
|
+
* Unlike `getOrConnect`, this never reuses or caches connections — every
|
|
146
|
+
* call produces a dedicated socket with its own close lifecycle. Use this
|
|
147
|
+
* when you want each logical client to own its connection (the common
|
|
148
|
+
* case for `createClient()` users).
|
|
149
|
+
*/
|
|
150
|
+
static async connect(config) {
|
|
151
|
+
return this.openSocket(this.buildUrl(config), this.buildHeaders(config), config.domain);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Reconstruct the typed error the server threw during handshake.
|
|
155
|
+
*
|
|
156
|
+
* The server sends `error.toJSON()` which has `{success:false, type, data?, context?}`.
|
|
157
|
+
* System errors (NOT_AUTHORIZED, FORBIDDEN, VALIDATION_ERROR, etc.) are reconstructed
|
|
158
|
+
* as real instances so callers can `.toBeError(NOT_AUTHORIZED)`. Anything we can't
|
|
159
|
+
* identify (non-ERROR throw, custom error class the client doesn't know) falls back
|
|
160
|
+
* to SERVER_ERROR carrying the original payload for inspection.
|
|
161
|
+
*/
|
|
162
|
+
static handshakeErrorFrom(payload) {
|
|
163
|
+
if (payload && typeof payload === 'object' && typeof payload.type === 'string') {
|
|
164
|
+
return GGContractExecutor.createErrorObj(payload);
|
|
165
|
+
}
|
|
166
|
+
return new SERVER_ERROR({
|
|
167
|
+
displayMessage: 'WebSocket handshake failed',
|
|
168
|
+
originalError: payload,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
static buildUrl(config) {
|
|
172
|
+
let fullUrl = config.domain + config.path;
|
|
173
|
+
if (config.query) {
|
|
174
|
+
const queryEntries = Object.entries(config.query).map(([key, value]) => [key, String(value)]);
|
|
175
|
+
fullUrl += '?' + new URLSearchParams(queryEntries).toString();
|
|
176
|
+
}
|
|
177
|
+
return fullUrl;
|
|
178
|
+
}
|
|
179
|
+
static async openSocket(fullUrl, headers, domain) {
|
|
180
|
+
const adapterClass = await this.ensureAdapter();
|
|
181
|
+
return new Promise((resolve, reject) => {
|
|
182
|
+
const adapter = new adapterClass(fullUrl);
|
|
183
|
+
adapter.onOpen(async () => {
|
|
184
|
+
try {
|
|
185
|
+
const context = new GGContext("ws-client-connection");
|
|
186
|
+
await context.run(async () => {
|
|
187
|
+
GG_TRACE.init();
|
|
188
|
+
GG_WS_CONNECTION.set({
|
|
189
|
+
port: undefined,
|
|
190
|
+
path: domain
|
|
191
|
+
});
|
|
192
|
+
adapter.send(Message.create(MessageType.HANDSHAKE, "", "", headers));
|
|
193
|
+
await withTimeout(new Promise((handshakeResolve, handshakeReject) => {
|
|
194
|
+
const onMessage = (data) => {
|
|
195
|
+
const msg = Message.parse(data);
|
|
196
|
+
if (!msg)
|
|
197
|
+
return;
|
|
198
|
+
if (msg.type === MessageType.HANDSHAKE_OK) {
|
|
199
|
+
adapter.offMessage(onMessage);
|
|
200
|
+
handshakeResolve();
|
|
201
|
+
}
|
|
202
|
+
else if (msg.type === MessageType.HANDSHAKE_ERR) {
|
|
203
|
+
adapter.offMessage(onMessage);
|
|
204
|
+
handshakeReject(this.handshakeErrorFrom(msg.data));
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
adapter.onMessage(onMessage);
|
|
208
|
+
}), 5000, 'Handshake timeout');
|
|
209
|
+
resolve(new GGSocket(adapter, { connectionContext: context }));
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
reject(error);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
adapter.onError((error) => {
|
|
217
|
+
reject(error);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
}
|
|
202
221
|
}
|
|
203
222
|
//# sourceMappingURL=GGSocketPool.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GGSocketPool.js","sourceRoot":"","sources":["../../../src/client/GGSocketPool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,oBAAoB,CAAC;AAG5C,OAAO,EAAC,gBAAgB,EAAC,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAC,OAAO,EAAE,WAAW,EAAC,MAAM,yBAAyB,CAAC;AAC7D,OAAO,
|
|
1
|
+
{"version":3,"file":"GGSocketPool.js","sourceRoot":"","sources":["../../../src/client/GGSocketPool.ts"],"names":[],"mappings":"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"}
|