@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
|
@@ -3,7 +3,7 @@ import {GGWebSocketHandshakeContext, GGWebSocketMiddleware} from "../schema/GGWe
|
|
|
3
3
|
import {SocketAdapter} from "../socket/SocketAdapter";
|
|
4
4
|
import {GG_WS_CONNECTION} from "../server/GG_WS_CONNECTION";
|
|
5
5
|
import {Message, MessageType} from "../socket/SocketMessage";
|
|
6
|
-
import {GGValidator, SERVER_ERROR} from "@grest-ts/schema";
|
|
6
|
+
import {GGContractExecutor, GGValidator, SERVER_ERROR} from "@grest-ts/schema";
|
|
7
7
|
import {withTimeout} from "@grest-ts/common";
|
|
8
8
|
import {GGContext} from "@grest-ts/context";
|
|
9
9
|
import {GG_TRACE} from "@grest-ts/trace";
|
|
@@ -141,21 +141,13 @@ export class GGSocketPool {
|
|
|
141
141
|
static async getOrConnect<Query>(
|
|
142
142
|
config: GGSocketPoolConfig<Query>
|
|
143
143
|
): Promise<GGSocket> {
|
|
144
|
-
// Build headers from middlewares
|
|
145
144
|
const headers = this.buildHeaders(config);
|
|
146
|
-
|
|
147
|
-
// Build full URL with query string if provided
|
|
148
|
-
let fullUrl = config.domain + config.path;
|
|
149
|
-
if (config.query) {
|
|
150
|
-
const queryEntries: [string, string][] = Object.entries(config.query).map(([key, value]) => [key, String(value)]);
|
|
151
|
-
fullUrl += '?' + new URLSearchParams(queryEntries).toString();
|
|
152
|
-
}
|
|
145
|
+
const fullUrl = this.buildUrl(config);
|
|
153
146
|
|
|
154
147
|
// Create connection key based on URL + headers
|
|
155
148
|
const headerKey = Object.entries(headers).sort().map(([k, v]) => `${k}=${v}`).join('&');
|
|
156
149
|
const key = fullUrl + "::" + headerKey;
|
|
157
150
|
|
|
158
|
-
// Check for existing connection first
|
|
159
151
|
if (this.sockets.has(key)) {
|
|
160
152
|
return this.sockets.get(key);
|
|
161
153
|
}
|
|
@@ -163,82 +155,107 @@ export class GGSocketPool {
|
|
|
163
155
|
return this.pendingSockets.get(key);
|
|
164
156
|
}
|
|
165
157
|
|
|
166
|
-
|
|
167
|
-
// This ensures that concurrent calls will see the pending promise
|
|
168
|
-
const connectionPromise = (async () => {
|
|
169
|
-
// Ensure adapter is loaded (this is async but safely inside the promise)
|
|
170
|
-
const adapterClass = await this.ensureAdapter();
|
|
171
|
-
|
|
172
|
-
return new Promise<GGSocket>((resolve, reject) => {
|
|
173
|
-
const adapter = new adapterClass(fullUrl);
|
|
174
|
-
adapter.onOpen(async () => {
|
|
175
|
-
try {
|
|
176
|
-
const context = new GGContext("ws-client-connection");
|
|
177
|
-
await context.run(async () => {
|
|
178
|
-
GG_TRACE.init();
|
|
179
|
-
GG_WS_CONNECTION.set({
|
|
180
|
-
port: undefined,
|
|
181
|
-
path: config.domain
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
// Send handshake with headers
|
|
185
|
-
adapter.send(Message.create(MessageType.HANDSHAKE, "", "", headers));
|
|
186
|
-
|
|
187
|
-
// Wait for handshake response
|
|
188
|
-
await withTimeout(
|
|
189
|
-
new Promise<void>((handshakeResolve, handshakeReject) => {
|
|
190
|
-
const onMessage = (data: string) => {
|
|
191
|
-
const msg = Message.parse(data);
|
|
192
|
-
if (!msg) return;
|
|
193
|
-
|
|
194
|
-
if (msg.type === MessageType.HANDSHAKE_OK) {
|
|
195
|
-
adapter.offMessage(onMessage);
|
|
196
|
-
handshakeResolve();
|
|
197
|
-
} else if (msg.type === MessageType.HANDSHAKE_ERR) {
|
|
198
|
-
adapter.offMessage(onMessage);
|
|
199
|
-
handshakeReject(new SERVER_ERROR({
|
|
200
|
-
displayMessage: 'WebSocket handshake failed',
|
|
201
|
-
originalError: msg.data
|
|
202
|
-
}));
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
adapter.onMessage(onMessage);
|
|
206
|
-
}),
|
|
207
|
-
5000,
|
|
208
|
-
'Handshake timeout'
|
|
209
|
-
);
|
|
210
|
-
resolve(new GGSocket(adapter, {connectionContext: context}));
|
|
211
|
-
});
|
|
212
|
-
} catch (error) {
|
|
213
|
-
reject(error);
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
adapter.onError((error: Error) => {
|
|
217
|
-
reject(error);
|
|
218
|
-
});
|
|
219
|
-
});
|
|
220
|
-
})();
|
|
221
|
-
|
|
222
|
-
// Store the pending promise IMMEDIATELY (before awaiting)
|
|
158
|
+
const connectionPromise = this.openSocket(fullUrl, headers, config.domain);
|
|
223
159
|
this.pendingSockets.set(key, connectionPromise);
|
|
224
160
|
|
|
225
161
|
try {
|
|
226
162
|
const socket = await connectionPromise;
|
|
227
|
-
|
|
228
|
-
// Store the connection
|
|
229
163
|
this.sockets.set(key, socket);
|
|
230
164
|
this.pendingSockets.delete(key);
|
|
231
|
-
|
|
232
|
-
// Clean up on close
|
|
233
165
|
socket.onClose(() => {
|
|
234
166
|
this.sockets.delete(key);
|
|
235
167
|
});
|
|
236
|
-
|
|
237
168
|
return socket;
|
|
238
169
|
} catch (error) {
|
|
239
|
-
// Clean up failed connection attempt
|
|
240
170
|
this.pendingSockets.delete(key);
|
|
241
171
|
throw error;
|
|
242
172
|
}
|
|
243
173
|
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Establish a fresh, un-pooled WebSocket connection.
|
|
177
|
+
*
|
|
178
|
+
* Unlike `getOrConnect`, this never reuses or caches connections — every
|
|
179
|
+
* call produces a dedicated socket with its own close lifecycle. Use this
|
|
180
|
+
* when you want each logical client to own its connection (the common
|
|
181
|
+
* case for `createClient()` users).
|
|
182
|
+
*/
|
|
183
|
+
static async connect<Query>(
|
|
184
|
+
config: GGSocketPoolConfig<Query>
|
|
185
|
+
): Promise<GGSocket> {
|
|
186
|
+
return this.openSocket(this.buildUrl(config), this.buildHeaders(config), config.domain);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Reconstruct the typed error the server threw during handshake.
|
|
191
|
+
*
|
|
192
|
+
* The server sends `error.toJSON()` which has `{success:false, type, data?, context?}`.
|
|
193
|
+
* System errors (NOT_AUTHORIZED, FORBIDDEN, VALIDATION_ERROR, etc.) are reconstructed
|
|
194
|
+
* as real instances so callers can `.toBeError(NOT_AUTHORIZED)`. Anything we can't
|
|
195
|
+
* identify (non-ERROR throw, custom error class the client doesn't know) falls back
|
|
196
|
+
* to SERVER_ERROR carrying the original payload for inspection.
|
|
197
|
+
*/
|
|
198
|
+
private static handshakeErrorFrom(payload: any): Error {
|
|
199
|
+
if (payload && typeof payload === 'object' && typeof payload.type === 'string') {
|
|
200
|
+
return GGContractExecutor.createErrorObj(payload) as unknown as Error;
|
|
201
|
+
}
|
|
202
|
+
return new SERVER_ERROR({
|
|
203
|
+
displayMessage: 'WebSocket handshake failed',
|
|
204
|
+
originalError: payload,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private static buildUrl(config: GGSocketPoolConfig<any>): string {
|
|
209
|
+
let fullUrl = config.domain + config.path;
|
|
210
|
+
if (config.query) {
|
|
211
|
+
const queryEntries: [string, string][] = Object.entries(config.query).map(([key, value]) => [key, String(value)]);
|
|
212
|
+
fullUrl += '?' + new URLSearchParams(queryEntries).toString();
|
|
213
|
+
}
|
|
214
|
+
return fullUrl;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private static async openSocket(fullUrl: string, headers: Record<string, string>, domain: string): Promise<GGSocket> {
|
|
218
|
+
const adapterClass = await this.ensureAdapter();
|
|
219
|
+
return new Promise<GGSocket>((resolve, reject) => {
|
|
220
|
+
const adapter = new adapterClass(fullUrl);
|
|
221
|
+
adapter.onOpen(async () => {
|
|
222
|
+
try {
|
|
223
|
+
const context = new GGContext("ws-client-connection");
|
|
224
|
+
await context.run(async () => {
|
|
225
|
+
GG_TRACE.init();
|
|
226
|
+
GG_WS_CONNECTION.set({
|
|
227
|
+
port: undefined,
|
|
228
|
+
path: domain
|
|
229
|
+
});
|
|
230
|
+
adapter.send(Message.create(MessageType.HANDSHAKE, "", "", headers));
|
|
231
|
+
await withTimeout(
|
|
232
|
+
new Promise<void>((handshakeResolve, handshakeReject) => {
|
|
233
|
+
const onMessage = (data: string) => {
|
|
234
|
+
const msg = Message.parse(data);
|
|
235
|
+
if (!msg) return;
|
|
236
|
+
|
|
237
|
+
if (msg.type === MessageType.HANDSHAKE_OK) {
|
|
238
|
+
adapter.offMessage(onMessage);
|
|
239
|
+
handshakeResolve();
|
|
240
|
+
} else if (msg.type === MessageType.HANDSHAKE_ERR) {
|
|
241
|
+
adapter.offMessage(onMessage);
|
|
242
|
+
handshakeReject(this.handshakeErrorFrom(msg.data));
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
adapter.onMessage(onMessage);
|
|
246
|
+
}),
|
|
247
|
+
5000,
|
|
248
|
+
'Handshake timeout'
|
|
249
|
+
);
|
|
250
|
+
resolve(new GGSocket(adapter, {connectionContext: context}));
|
|
251
|
+
});
|
|
252
|
+
} catch (error) {
|
|
253
|
+
reject(error);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
adapter.onError((error: Error) => {
|
|
257
|
+
reject(error);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
}
|
|
244
261
|
}
|