@grest-ts/websocket 0.0.5 → 0.0.7
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/LICENSE +21 -21
- package/README.md +5 -0
- package/dist/tsconfig.publish.tsbuildinfo +1 -1
- package/package.json +12 -12
- package/src/adapter/BrowserSocketAdapter.ts +63 -63
- package/src/adapter/NodeSocketAdapter.ts +67 -67
- package/src/adapter/getDefaultAdapter.ts +13 -13
- package/src/client/GGSocketClient.ts +25 -25
- package/src/client/GGSocketPool.ts +244 -244
- package/src/index-browser.ts +20 -20
- package/src/index-node.ts +28 -28
- package/src/schema/GGWebSocketMiddleware.ts +43 -43
- package/src/schema/GGWebSocketSchema.ts +57 -57
- package/src/schema/webSocketSchema.ts +109 -109
- package/src/server/GGSocketServer.ts +217 -217
- package/src/server/GGWebSocketMetrics.ts +58 -58
- package/src/server/GGWebSocketSchema.startServer.ts +136 -136
- package/src/server/GG_WS_CONNECTION.ts +10 -10
- package/src/server/GG_WS_MESSAGE.ts +9 -9
- package/src/socket/GGSocket.ts +394 -394
- package/src/socket/SocketAdapter.ts +21 -21
- package/src/socket/SocketMessage.ts +97 -97
- package/src/socket/WebSocketTypes.ts +19 -19
- package/src/socket/utils/PendingRequestsMap.ts +128 -128
|
@@ -1,136 +1,136 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Server extension for WebSocketSchema - adds startServer and register methods
|
|
3
|
-
* This file should only be imported in server (Node.js) context
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type {GGSocket} from "../socket/GGSocket"
|
|
7
|
-
import {GGWebSocketSchema} from "../schema/GGWebSocketSchema";
|
|
8
|
-
import {GGWebSocketMiddleware} from "../schema/GGWebSocketMiddleware";
|
|
9
|
-
import {GGSocketServer} from "./GGSocketServer";
|
|
10
|
-
import {GGLocator} from "@grest-ts/locator";
|
|
11
|
-
import {WebSocketIncoming, WebSocketOutgoing} from "../socket/WebSocketTypes";
|
|
12
|
-
import {GG_HTTP_SERVER, GGHttpServer} from "@grest-ts/http";
|
|
13
|
-
|
|
14
|
-
export interface WebSocketSchemaConfig {
|
|
15
|
-
/**
|
|
16
|
-
* The HTTP server adapter to attach the WebSocket server to.
|
|
17
|
-
* If not provided, will look up from locator.
|
|
18
|
-
*/
|
|
19
|
-
http?: GGHttpServer;
|
|
20
|
-
/**
|
|
21
|
-
* Additional middlewares to apply to all connections.
|
|
22
|
-
*/
|
|
23
|
-
middlewares?: GGWebSocketMiddleware[];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
declare module "../schema/GGWebSocketSchema" {
|
|
27
|
-
interface GGWebSocketSchema<TClientToServer, TServerToClient, TContext = {}, TQuery = undefined, TClientToServerImpl = TClientToServer> {
|
|
28
|
-
/**
|
|
29
|
-
* Start the WebSocket server for this API.
|
|
30
|
-
*/
|
|
31
|
-
startServer(
|
|
32
|
-
onConnection: (incoming: WebSocketIncoming<TClientToServerImpl>, outgoing: WebSocketOutgoing<TServerToClient>) => void,
|
|
33
|
-
config: WebSocketSchemaConfig
|
|
34
|
-
): GGSocketServer<TContext, TQuery>
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Register this WebSocket API with the default HTTP server.
|
|
38
|
-
* Uses GGHttpServerAdapter from locator if not explicitly provided.
|
|
39
|
-
*/
|
|
40
|
-
register(
|
|
41
|
-
onConnection: (incoming: WebSocketIncoming<TClientToServerImpl>, outgoing: WebSocketOutgoing<TServerToClient>) => void,
|
|
42
|
-
config?: WebSocketSchemaConfig
|
|
43
|
-
): void
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
GGWebSocketSchema.prototype.startServer = function (
|
|
48
|
-
this: GGWebSocketSchema<any, any, any, any>,
|
|
49
|
-
onConnection: any,
|
|
50
|
-
config: WebSocketSchemaConfig
|
|
51
|
-
): GGSocketServer<any, any> {
|
|
52
|
-
const contract = this.contract
|
|
53
|
-
if (!contract) {
|
|
54
|
-
throw new Error(`WebSocketSchema "${this.name}" has no contract.`)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const normalizedPath = this.path.startsWith('/') ? this.path : '/' + this.path
|
|
58
|
-
const schemaName = this.name
|
|
59
|
-
const http = config.http ?? GGLocator.getScope().get(GG_HTTP_SERVER);
|
|
60
|
-
|
|
61
|
-
// @TODO We might want some lookup here based on path/middlewares etc. If I use same socket for multiple paths, we need to reuse also same GGSocketServer.
|
|
62
|
-
const socketServer = new GGSocketServer(http, {
|
|
63
|
-
apiName: schemaName,
|
|
64
|
-
path: normalizedPath,
|
|
65
|
-
middlewares: [...this.middlewares, ...(config?.middlewares ?? [])]
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
socketServer.onConnection((socket: GGSocket) => {
|
|
69
|
-
const clientToServerContract = contract.clientToServer
|
|
70
|
-
const serverToClientContract = contract.serverToClient
|
|
71
|
-
|
|
72
|
-
const incoming: any = {
|
|
73
|
-
on(handlers: any) {
|
|
74
|
-
const impl: Record<string, any> = {};
|
|
75
|
-
for (const methodName of Object.keys(clientToServerContract.methods)) {
|
|
76
|
-
const methodDef = clientToServerContract.methods[methodName] as any;
|
|
77
|
-
const params = methodDef.params;
|
|
78
|
-
impl[methodName] = (data: any) => {
|
|
79
|
-
// If method has params info, unpack data object to positional args
|
|
80
|
-
if (params && params.length > 0 && data && typeof data === 'object') {
|
|
81
|
-
const args = params.map((p: any) => data[p.name]);
|
|
82
|
-
return handlers[methodName](...args);
|
|
83
|
-
}
|
|
84
|
-
// Single or no parameter - pass directly
|
|
85
|
-
return handlers[methodName](data);
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const incomingInstance = clientToServerContract.implement(impl);
|
|
90
|
-
|
|
91
|
-
for (const methodName of Object.keys(clientToServerContract.methods)) {
|
|
92
|
-
socket.registerHandler({
|
|
93
|
-
path: `${schemaName}.${methodName}`,
|
|
94
|
-
handler: (incomingInstance as any)[methodName]
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const impl: Record<string, any> = {};
|
|
101
|
-
for (const methodName of Object.keys(serverToClientContract.methods)) {
|
|
102
|
-
const method = serverToClientContract.methods[methodName];
|
|
103
|
-
const expectsResponse = 'success' in method;
|
|
104
|
-
impl[methodName] = (data: any) => {
|
|
105
|
-
return socket.send(`${schemaName}.${methodName}`, data, expectsResponse);
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const outgoingInstance = serverToClientContract.implement(impl);
|
|
110
|
-
(outgoingInstance as any).onClose = (callback: () => void) => {
|
|
111
|
-
socket.onClose(callback)
|
|
112
|
-
}
|
|
113
|
-
onConnection(incoming, outgoingInstance)
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
return socketServer;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
GGWebSocketSchema.prototype.register = function (
|
|
120
|
-
this: GGWebSocketSchema<any, any, any, any>,
|
|
121
|
-
onConnection: any,
|
|
122
|
-
config?: WebSocketSchemaConfig
|
|
123
|
-
): void {
|
|
124
|
-
let httpServer = config?.http;
|
|
125
|
-
if (!httpServer) {
|
|
126
|
-
httpServer = GGLocator.getScope().get(GG_HTTP_SERVER);
|
|
127
|
-
}
|
|
128
|
-
if (!httpServer) {
|
|
129
|
-
throw new Error(`No HTTP server found. Make sure to register GGHttpServerAdapter in the scope or pass it via config`)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
this.startServer(onConnection, {
|
|
133
|
-
http: httpServer,
|
|
134
|
-
middlewares: config?.middlewares
|
|
135
|
-
});
|
|
136
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Server extension for WebSocketSchema - adds startServer and register methods
|
|
3
|
+
* This file should only be imported in server (Node.js) context
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {GGSocket} from "../socket/GGSocket"
|
|
7
|
+
import {GGWebSocketSchema} from "../schema/GGWebSocketSchema";
|
|
8
|
+
import {GGWebSocketMiddleware} from "../schema/GGWebSocketMiddleware";
|
|
9
|
+
import {GGSocketServer} from "./GGSocketServer";
|
|
10
|
+
import {GGLocator} from "@grest-ts/locator";
|
|
11
|
+
import {WebSocketIncoming, WebSocketOutgoing} from "../socket/WebSocketTypes";
|
|
12
|
+
import {GG_HTTP_SERVER, GGHttpServer} from "@grest-ts/http";
|
|
13
|
+
|
|
14
|
+
export interface WebSocketSchemaConfig {
|
|
15
|
+
/**
|
|
16
|
+
* The HTTP server adapter to attach the WebSocket server to.
|
|
17
|
+
* If not provided, will look up from locator.
|
|
18
|
+
*/
|
|
19
|
+
http?: GGHttpServer;
|
|
20
|
+
/**
|
|
21
|
+
* Additional middlewares to apply to all connections.
|
|
22
|
+
*/
|
|
23
|
+
middlewares?: GGWebSocketMiddleware[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
declare module "../schema/GGWebSocketSchema" {
|
|
27
|
+
interface GGWebSocketSchema<TClientToServer, TServerToClient, TContext = {}, TQuery = undefined, TClientToServerImpl = TClientToServer> {
|
|
28
|
+
/**
|
|
29
|
+
* Start the WebSocket server for this API.
|
|
30
|
+
*/
|
|
31
|
+
startServer(
|
|
32
|
+
onConnection: (incoming: WebSocketIncoming<TClientToServerImpl>, outgoing: WebSocketOutgoing<TServerToClient>) => void,
|
|
33
|
+
config: WebSocketSchemaConfig
|
|
34
|
+
): GGSocketServer<TContext, TQuery>
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Register this WebSocket API with the default HTTP server.
|
|
38
|
+
* Uses GGHttpServerAdapter from locator if not explicitly provided.
|
|
39
|
+
*/
|
|
40
|
+
register(
|
|
41
|
+
onConnection: (incoming: WebSocketIncoming<TClientToServerImpl>, outgoing: WebSocketOutgoing<TServerToClient>) => void,
|
|
42
|
+
config?: WebSocketSchemaConfig
|
|
43
|
+
): void
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
GGWebSocketSchema.prototype.startServer = function (
|
|
48
|
+
this: GGWebSocketSchema<any, any, any, any>,
|
|
49
|
+
onConnection: any,
|
|
50
|
+
config: WebSocketSchemaConfig
|
|
51
|
+
): GGSocketServer<any, any> {
|
|
52
|
+
const contract = this.contract
|
|
53
|
+
if (!contract) {
|
|
54
|
+
throw new Error(`WebSocketSchema "${this.name}" has no contract.`)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const normalizedPath = this.path.startsWith('/') ? this.path : '/' + this.path
|
|
58
|
+
const schemaName = this.name
|
|
59
|
+
const http = config.http ?? GGLocator.getScope().get(GG_HTTP_SERVER);
|
|
60
|
+
|
|
61
|
+
// @TODO We might want some lookup here based on path/middlewares etc. If I use same socket for multiple paths, we need to reuse also same GGSocketServer.
|
|
62
|
+
const socketServer = new GGSocketServer(http, {
|
|
63
|
+
apiName: schemaName,
|
|
64
|
+
path: normalizedPath,
|
|
65
|
+
middlewares: [...this.middlewares, ...(config?.middlewares ?? [])]
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
socketServer.onConnection((socket: GGSocket) => {
|
|
69
|
+
const clientToServerContract = contract.clientToServer
|
|
70
|
+
const serverToClientContract = contract.serverToClient
|
|
71
|
+
|
|
72
|
+
const incoming: any = {
|
|
73
|
+
on(handlers: any) {
|
|
74
|
+
const impl: Record<string, any> = {};
|
|
75
|
+
for (const methodName of Object.keys(clientToServerContract.methods)) {
|
|
76
|
+
const methodDef = clientToServerContract.methods[methodName] as any;
|
|
77
|
+
const params = methodDef.params;
|
|
78
|
+
impl[methodName] = (data: any) => {
|
|
79
|
+
// If method has params info, unpack data object to positional args
|
|
80
|
+
if (params && params.length > 0 && data && typeof data === 'object') {
|
|
81
|
+
const args = params.map((p: any) => data[p.name]);
|
|
82
|
+
return handlers[methodName](...args);
|
|
83
|
+
}
|
|
84
|
+
// Single or no parameter - pass directly
|
|
85
|
+
return handlers[methodName](data);
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const incomingInstance = clientToServerContract.implement(impl);
|
|
90
|
+
|
|
91
|
+
for (const methodName of Object.keys(clientToServerContract.methods)) {
|
|
92
|
+
socket.registerHandler({
|
|
93
|
+
path: `${schemaName}.${methodName}`,
|
|
94
|
+
handler: (incomingInstance as any)[methodName]
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const impl: Record<string, any> = {};
|
|
101
|
+
for (const methodName of Object.keys(serverToClientContract.methods)) {
|
|
102
|
+
const method = serverToClientContract.methods[methodName];
|
|
103
|
+
const expectsResponse = 'success' in method;
|
|
104
|
+
impl[methodName] = (data: any) => {
|
|
105
|
+
return socket.send(`${schemaName}.${methodName}`, data, expectsResponse);
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const outgoingInstance = serverToClientContract.implement(impl);
|
|
110
|
+
(outgoingInstance as any).onClose = (callback: () => void) => {
|
|
111
|
+
socket.onClose(callback)
|
|
112
|
+
}
|
|
113
|
+
onConnection(incoming, outgoingInstance)
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return socketServer;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
GGWebSocketSchema.prototype.register = function (
|
|
120
|
+
this: GGWebSocketSchema<any, any, any, any>,
|
|
121
|
+
onConnection: any,
|
|
122
|
+
config?: WebSocketSchemaConfig
|
|
123
|
+
): void {
|
|
124
|
+
let httpServer = config?.http;
|
|
125
|
+
if (!httpServer) {
|
|
126
|
+
httpServer = GGLocator.getScope().get(GG_HTTP_SERVER);
|
|
127
|
+
}
|
|
128
|
+
if (!httpServer) {
|
|
129
|
+
throw new Error(`No HTTP server found. Make sure to register GGHttpServerAdapter in the scope or pass it via config`)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.startServer(onConnection, {
|
|
133
|
+
http: httpServer,
|
|
134
|
+
middlewares: config?.middlewares
|
|
135
|
+
});
|
|
136
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {GGContextKey} from "@grest-ts/context";
|
|
2
|
-
import {IsNumber, IsObject, IsString} from "@grest-ts/schema";
|
|
3
|
-
|
|
4
|
-
const IsWsConnectionContext = IsObject({
|
|
5
|
-
port: IsNumber.orUndefined,
|
|
6
|
-
path: IsString
|
|
7
|
-
});
|
|
8
|
-
export type WsConnectionContext = typeof IsWsConnectionContext.infer;
|
|
9
|
-
|
|
10
|
-
export const GG_WS_CONNECTION = new GGContextKey<WsConnectionContext>('ws-connection', IsWsConnectionContext);
|
|
1
|
+
import {GGContextKey} from "@grest-ts/context";
|
|
2
|
+
import {IsNumber, IsObject, IsString} from "@grest-ts/schema";
|
|
3
|
+
|
|
4
|
+
const IsWsConnectionContext = IsObject({
|
|
5
|
+
port: IsNumber.orUndefined,
|
|
6
|
+
path: IsString
|
|
7
|
+
});
|
|
8
|
+
export type WsConnectionContext = typeof IsWsConnectionContext.infer;
|
|
9
|
+
|
|
10
|
+
export const GG_WS_CONNECTION = new GGContextKey<WsConnectionContext>('ws-connection', IsWsConnectionContext);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {GGContextKey} from "@grest-ts/context";
|
|
2
|
-
import {IsObject, IsString} from "@grest-ts/schema";
|
|
3
|
-
|
|
4
|
-
const IsWsMessageContext = IsObject({
|
|
5
|
-
path: IsString
|
|
6
|
-
});
|
|
7
|
-
export type WsMessageContext = typeof IsWsMessageContext.infer;
|
|
8
|
-
|
|
9
|
-
export const GG_WS_MESSAGE = new GGContextKey<WsMessageContext>('ws-message', IsWsMessageContext);
|
|
1
|
+
import {GGContextKey} from "@grest-ts/context";
|
|
2
|
+
import {IsObject, IsString} from "@grest-ts/schema";
|
|
3
|
+
|
|
4
|
+
const IsWsMessageContext = IsObject({
|
|
5
|
+
path: IsString
|
|
6
|
+
});
|
|
7
|
+
export type WsMessageContext = typeof IsWsMessageContext.infer;
|
|
8
|
+
|
|
9
|
+
export const GG_WS_MESSAGE = new GGContextKey<WsMessageContext>('ws-message', IsWsMessageContext);
|