@grest-ts/websocket 0.0.6 → 0.0.8
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/dist/tsconfig.publish.tsbuildinfo +1 -1
- package/package.json +13 -13
- 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,244 +1,244 @@
|
|
|
1
|
-
import {GGSocket} from '../socket/GGSocket';
|
|
2
|
-
import {GGWebSocketHandshakeContext, GGWebSocketMiddleware} from "../schema/GGWebSocketMiddleware";
|
|
3
|
-
import {SocketAdapter} from "../socket/SocketAdapter";
|
|
4
|
-
import {GG_WS_CONNECTION} from "../server/GG_WS_CONNECTION";
|
|
5
|
-
import {Message, MessageType} from "../socket/SocketMessage";
|
|
6
|
-
import {GGValidator, SERVER_ERROR} from "@grest-ts/schema";
|
|
7
|
-
import {withTimeout} from "@grest-ts/common";
|
|
8
|
-
import {GGContext} from "@grest-ts/context";
|
|
9
|
-
import {GG_TRACE} from "@grest-ts/trace";
|
|
10
|
-
import {getDefaultAdapter} from "../adapter/getDefaultAdapter";
|
|
11
|
-
|
|
12
|
-
export interface GGSocketPoolConfig<Query> {
|
|
13
|
-
domain: string,
|
|
14
|
-
path: string,
|
|
15
|
-
query?: Query
|
|
16
|
-
queryValidator?: GGValidator<Query>
|
|
17
|
-
middlewares?: readonly GGWebSocketMiddleware[]
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Connection pool for WebSocket connections
|
|
22
|
-
* Reuses existing connections when the same URL + headers combination is requested
|
|
23
|
-
*/
|
|
24
|
-
export class GGSocketPool {
|
|
25
|
-
private static sockets = new Map<string, GGSocket>();
|
|
26
|
-
private static pendingSockets = new Map<string, Promise<GGSocket>>();
|
|
27
|
-
private static adapter: any = null;
|
|
28
|
-
private static adapterPromise: Promise<any> | null = null;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Get the current number of active connections in the pool
|
|
32
|
-
*/
|
|
33
|
-
public static get size(): number {
|
|
34
|
-
return this.sockets.size;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Get the current number of pending connections being established
|
|
39
|
-
*/
|
|
40
|
-
public static get pendingSize(): number {
|
|
41
|
-
return this.pendingSockets.size;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Close all pooled connections gracefully.
|
|
46
|
-
* Waits for pending connections to establish before closing them.
|
|
47
|
-
* @param graceful - If true, calls teardown() on each socket allowing pending requests to complete.
|
|
48
|
-
* If false, calls close() for immediate termination.
|
|
49
|
-
*/
|
|
50
|
-
public static async closeAll(graceful: boolean = true): Promise<void> {
|
|
51
|
-
// Wait for any pending connections to establish first
|
|
52
|
-
const pendingPromises = Array.from(this.pendingSockets.values());
|
|
53
|
-
if (pendingPromises.length > 0) {
|
|
54
|
-
await Promise.allSettled(pendingPromises);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Close all active connections
|
|
58
|
-
const sockets = Array.from(this.sockets.values());
|
|
59
|
-
if (graceful) {
|
|
60
|
-
await Promise.allSettled(sockets.map(socket => socket.teardown()));
|
|
61
|
-
} else {
|
|
62
|
-
sockets.forEach(socket => socket.close());
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Clear the maps (onClose handlers should have already removed them,
|
|
66
|
-
// but clear explicitly to handle any edge cases)
|
|
67
|
-
this.sockets.clear();
|
|
68
|
-
this.pendingSockets.clear();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Clear all maps without closing connections.
|
|
73
|
-
* WARNING: This will cause memory leaks if connections are still active.
|
|
74
|
-
* Only use for testing purposes.
|
|
75
|
-
*/
|
|
76
|
-
public static __clearForTesting(): void {
|
|
77
|
-
this.sockets.clear();
|
|
78
|
-
this.pendingSockets.clear();
|
|
79
|
-
this.adapter = null;
|
|
80
|
-
this.adapterPromise = null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Remove a specific connection from the pool by its key.
|
|
85
|
-
* Does NOT close the socket - just removes it from the pool.
|
|
86
|
-
* @returns true if the connection was found and removed, false otherwise
|
|
87
|
-
*/
|
|
88
|
-
public static removeFromPool(key: string): boolean {
|
|
89
|
-
return this.sockets.delete(key);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Get all connection keys currently in the pool (for debugging/monitoring)
|
|
94
|
-
*/
|
|
95
|
-
public static getConnectionKeys(): string[] {
|
|
96
|
-
return Array.from(this.sockets.keys());
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Ensure adapter is loaded (lazy initialization)
|
|
101
|
-
*/
|
|
102
|
-
private static async ensureAdapter(): Promise<any> {
|
|
103
|
-
if (this.adapter) {
|
|
104
|
-
return this.adapter;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (!this.adapterPromise) {
|
|
108
|
-
this.adapterPromise = getDefaultAdapter();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Always await the promise to handle race conditions
|
|
112
|
-
this.adapter = await this.adapterPromise;
|
|
113
|
-
return this.adapter;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
public static setAdapter(adapter: new(args: any, options?: any) => SocketAdapter) {
|
|
117
|
-
this.adapter = adapter;
|
|
118
|
-
this.adapterPromise = Promise.resolve(adapter);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Build headers from middlewares' updateHandshake()
|
|
123
|
-
*/
|
|
124
|
-
private static buildHeaders(config: GGSocketPoolConfig<any>): Record<string, string> {
|
|
125
|
-
if (!config.middlewares) {
|
|
126
|
-
return {};
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const handshakeContext: GGWebSocketHandshakeContext = {
|
|
130
|
-
headers: {},
|
|
131
|
-
queryArgs: (config.query as Record<string, string>) ?? {}
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
for (const middleware of config.middlewares) {
|
|
135
|
-
middleware.updateHandshake?.(handshakeContext);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return handshakeContext.headers;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
static async getOrConnect<Query>(
|
|
142
|
-
config: GGSocketPoolConfig<Query>
|
|
143
|
-
): Promise<GGSocket> {
|
|
144
|
-
// Build headers from middlewares
|
|
145
|
-
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
|
-
}
|
|
153
|
-
|
|
154
|
-
// Create connection key based on URL + headers
|
|
155
|
-
const headerKey = Object.entries(headers).sort().map(([k, v]) => `${k}=${v}`).join('&');
|
|
156
|
-
const key = fullUrl + "::" + headerKey;
|
|
157
|
-
|
|
158
|
-
// Check for existing connection first
|
|
159
|
-
if (this.sockets.has(key)) {
|
|
160
|
-
return this.sockets.get(key);
|
|
161
|
-
}
|
|
162
|
-
if (this.pendingSockets.has(key)) {
|
|
163
|
-
return this.pendingSockets.get(key);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Create the connection promise BEFORE any async operations to prevent race conditions
|
|
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)
|
|
223
|
-
this.pendingSockets.set(key, connectionPromise);
|
|
224
|
-
|
|
225
|
-
try {
|
|
226
|
-
const socket = await connectionPromise;
|
|
227
|
-
|
|
228
|
-
// Store the connection
|
|
229
|
-
this.sockets.set(key, socket);
|
|
230
|
-
this.pendingSockets.delete(key);
|
|
231
|
-
|
|
232
|
-
// Clean up on close
|
|
233
|
-
socket.onClose(() => {
|
|
234
|
-
this.sockets.delete(key);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
return socket;
|
|
238
|
-
} catch (error) {
|
|
239
|
-
// Clean up failed connection attempt
|
|
240
|
-
this.pendingSockets.delete(key);
|
|
241
|
-
throw error;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
1
|
+
import {GGSocket} from '../socket/GGSocket';
|
|
2
|
+
import {GGWebSocketHandshakeContext, GGWebSocketMiddleware} from "../schema/GGWebSocketMiddleware";
|
|
3
|
+
import {SocketAdapter} from "../socket/SocketAdapter";
|
|
4
|
+
import {GG_WS_CONNECTION} from "../server/GG_WS_CONNECTION";
|
|
5
|
+
import {Message, MessageType} from "../socket/SocketMessage";
|
|
6
|
+
import {GGValidator, SERVER_ERROR} from "@grest-ts/schema";
|
|
7
|
+
import {withTimeout} from "@grest-ts/common";
|
|
8
|
+
import {GGContext} from "@grest-ts/context";
|
|
9
|
+
import {GG_TRACE} from "@grest-ts/trace";
|
|
10
|
+
import {getDefaultAdapter} from "../adapter/getDefaultAdapter";
|
|
11
|
+
|
|
12
|
+
export interface GGSocketPoolConfig<Query> {
|
|
13
|
+
domain: string,
|
|
14
|
+
path: string,
|
|
15
|
+
query?: Query
|
|
16
|
+
queryValidator?: GGValidator<Query>
|
|
17
|
+
middlewares?: readonly GGWebSocketMiddleware[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Connection pool for WebSocket connections
|
|
22
|
+
* Reuses existing connections when the same URL + headers combination is requested
|
|
23
|
+
*/
|
|
24
|
+
export class GGSocketPool {
|
|
25
|
+
private static sockets = new Map<string, GGSocket>();
|
|
26
|
+
private static pendingSockets = new Map<string, Promise<GGSocket>>();
|
|
27
|
+
private static adapter: any = null;
|
|
28
|
+
private static adapterPromise: Promise<any> | null = null;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get the current number of active connections in the pool
|
|
32
|
+
*/
|
|
33
|
+
public static get size(): number {
|
|
34
|
+
return this.sockets.size;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get the current number of pending connections being established
|
|
39
|
+
*/
|
|
40
|
+
public static get pendingSize(): number {
|
|
41
|
+
return this.pendingSockets.size;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Close all pooled connections gracefully.
|
|
46
|
+
* Waits for pending connections to establish before closing them.
|
|
47
|
+
* @param graceful - If true, calls teardown() on each socket allowing pending requests to complete.
|
|
48
|
+
* If false, calls close() for immediate termination.
|
|
49
|
+
*/
|
|
50
|
+
public static async closeAll(graceful: boolean = true): Promise<void> {
|
|
51
|
+
// Wait for any pending connections to establish first
|
|
52
|
+
const pendingPromises = Array.from(this.pendingSockets.values());
|
|
53
|
+
if (pendingPromises.length > 0) {
|
|
54
|
+
await Promise.allSettled(pendingPromises);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Close all active connections
|
|
58
|
+
const sockets = Array.from(this.sockets.values());
|
|
59
|
+
if (graceful) {
|
|
60
|
+
await Promise.allSettled(sockets.map(socket => socket.teardown()));
|
|
61
|
+
} else {
|
|
62
|
+
sockets.forEach(socket => socket.close());
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Clear the maps (onClose handlers should have already removed them,
|
|
66
|
+
// but clear explicitly to handle any edge cases)
|
|
67
|
+
this.sockets.clear();
|
|
68
|
+
this.pendingSockets.clear();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Clear all maps without closing connections.
|
|
73
|
+
* WARNING: This will cause memory leaks if connections are still active.
|
|
74
|
+
* Only use for testing purposes.
|
|
75
|
+
*/
|
|
76
|
+
public static __clearForTesting(): void {
|
|
77
|
+
this.sockets.clear();
|
|
78
|
+
this.pendingSockets.clear();
|
|
79
|
+
this.adapter = null;
|
|
80
|
+
this.adapterPromise = null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Remove a specific connection from the pool by its key.
|
|
85
|
+
* Does NOT close the socket - just removes it from the pool.
|
|
86
|
+
* @returns true if the connection was found and removed, false otherwise
|
|
87
|
+
*/
|
|
88
|
+
public static removeFromPool(key: string): boolean {
|
|
89
|
+
return this.sockets.delete(key);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get all connection keys currently in the pool (for debugging/monitoring)
|
|
94
|
+
*/
|
|
95
|
+
public static getConnectionKeys(): string[] {
|
|
96
|
+
return Array.from(this.sockets.keys());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Ensure adapter is loaded (lazy initialization)
|
|
101
|
+
*/
|
|
102
|
+
private static async ensureAdapter(): Promise<any> {
|
|
103
|
+
if (this.adapter) {
|
|
104
|
+
return this.adapter;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!this.adapterPromise) {
|
|
108
|
+
this.adapterPromise = getDefaultAdapter();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Always await the promise to handle race conditions
|
|
112
|
+
this.adapter = await this.adapterPromise;
|
|
113
|
+
return this.adapter;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public static setAdapter(adapter: new(args: any, options?: any) => SocketAdapter) {
|
|
117
|
+
this.adapter = adapter;
|
|
118
|
+
this.adapterPromise = Promise.resolve(adapter);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Build headers from middlewares' updateHandshake()
|
|
123
|
+
*/
|
|
124
|
+
private static buildHeaders(config: GGSocketPoolConfig<any>): Record<string, string> {
|
|
125
|
+
if (!config.middlewares) {
|
|
126
|
+
return {};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const handshakeContext: GGWebSocketHandshakeContext = {
|
|
130
|
+
headers: {},
|
|
131
|
+
queryArgs: (config.query as Record<string, string>) ?? {}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
for (const middleware of config.middlewares) {
|
|
135
|
+
middleware.updateHandshake?.(handshakeContext);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return handshakeContext.headers;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
static async getOrConnect<Query>(
|
|
142
|
+
config: GGSocketPoolConfig<Query>
|
|
143
|
+
): Promise<GGSocket> {
|
|
144
|
+
// Build headers from middlewares
|
|
145
|
+
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
|
+
}
|
|
153
|
+
|
|
154
|
+
// Create connection key based on URL + headers
|
|
155
|
+
const headerKey = Object.entries(headers).sort().map(([k, v]) => `${k}=${v}`).join('&');
|
|
156
|
+
const key = fullUrl + "::" + headerKey;
|
|
157
|
+
|
|
158
|
+
// Check for existing connection first
|
|
159
|
+
if (this.sockets.has(key)) {
|
|
160
|
+
return this.sockets.get(key);
|
|
161
|
+
}
|
|
162
|
+
if (this.pendingSockets.has(key)) {
|
|
163
|
+
return this.pendingSockets.get(key);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Create the connection promise BEFORE any async operations to prevent race conditions
|
|
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)
|
|
223
|
+
this.pendingSockets.set(key, connectionPromise);
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
const socket = await connectionPromise;
|
|
227
|
+
|
|
228
|
+
// Store the connection
|
|
229
|
+
this.sockets.set(key, socket);
|
|
230
|
+
this.pendingSockets.delete(key);
|
|
231
|
+
|
|
232
|
+
// Clean up on close
|
|
233
|
+
socket.onClose(() => {
|
|
234
|
+
this.sockets.delete(key);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
return socket;
|
|
238
|
+
} catch (error) {
|
|
239
|
+
// Clean up failed connection attempt
|
|
240
|
+
this.pendingSockets.delete(key);
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
package/src/index-browser.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
// Type helpers
|
|
2
|
-
export * from "./socket/WebSocketTypes";
|
|
3
|
-
|
|
4
|
-
// Metrics
|
|
5
|
-
export * from "./server/GGWebSocketMetrics";
|
|
6
|
-
|
|
7
|
-
// Context
|
|
8
|
-
export * from "./server/GG_WS_CONNECTION";
|
|
9
|
-
export * from "./server/GG_WS_MESSAGE";
|
|
10
|
-
|
|
11
|
-
// Core
|
|
12
|
-
export * from "./socket/GGSocket";
|
|
13
|
-
|
|
14
|
-
// API Schema
|
|
15
|
-
export * from "./schema/webSocketSchema";
|
|
16
|
-
export * from "./schema/GGWebSocketSchema";
|
|
17
|
-
export * from "./schema/GGWebSocketMiddleware";
|
|
18
|
-
|
|
19
|
-
// Client
|
|
20
|
-
export * from "./client/GGSocketClient";
|
|
1
|
+
// Type helpers
|
|
2
|
+
export * from "./socket/WebSocketTypes";
|
|
3
|
+
|
|
4
|
+
// Metrics
|
|
5
|
+
export * from "./server/GGWebSocketMetrics";
|
|
6
|
+
|
|
7
|
+
// Context
|
|
8
|
+
export * from "./server/GG_WS_CONNECTION";
|
|
9
|
+
export * from "./server/GG_WS_MESSAGE";
|
|
10
|
+
|
|
11
|
+
// Core
|
|
12
|
+
export * from "./socket/GGSocket";
|
|
13
|
+
|
|
14
|
+
// API Schema
|
|
15
|
+
export * from "./schema/webSocketSchema";
|
|
16
|
+
export * from "./schema/GGWebSocketSchema";
|
|
17
|
+
export * from "./schema/GGWebSocketMiddleware";
|
|
18
|
+
|
|
19
|
+
// Client
|
|
20
|
+
export * from "./client/GGSocketClient";
|
|
21
21
|
export * from "./client/GGSocketPool";
|
package/src/index-node.ts
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
// Type helpers
|
|
2
|
-
export * from "./socket/WebSocketTypes";
|
|
3
|
-
|
|
4
|
-
// Metrics
|
|
5
|
-
export * from "./server/GGWebSocketMetrics";
|
|
6
|
-
|
|
7
|
-
// Context
|
|
8
|
-
export * from "./server/GG_WS_CONNECTION";
|
|
9
|
-
export * from "./server/GG_WS_MESSAGE";
|
|
10
|
-
|
|
11
|
-
// Core
|
|
12
|
-
export * from "./socket/GGSocket";
|
|
13
|
-
|
|
14
|
-
// API Schema
|
|
15
|
-
export * from "./schema/webSocketSchema";
|
|
16
|
-
export * from "./schema/GGWebSocketSchema";
|
|
17
|
-
export * from "./schema/GGWebSocketMiddleware";
|
|
18
|
-
|
|
19
|
-
// Server
|
|
20
|
-
export * from "./server/GGSocketServer";
|
|
21
|
-
export * from "./server/GGWebSocketSchema.startServer";
|
|
22
|
-
|
|
23
|
-
// Client
|
|
24
|
-
export * from "./client/GGSocketClient";
|
|
25
|
-
export * from "./client/GGSocketPool";
|
|
26
|
-
|
|
27
|
-
// Extensions
|
|
28
|
-
import "./server/GGWebSocketSchema.startServer";
|
|
1
|
+
// Type helpers
|
|
2
|
+
export * from "./socket/WebSocketTypes";
|
|
3
|
+
|
|
4
|
+
// Metrics
|
|
5
|
+
export * from "./server/GGWebSocketMetrics";
|
|
6
|
+
|
|
7
|
+
// Context
|
|
8
|
+
export * from "./server/GG_WS_CONNECTION";
|
|
9
|
+
export * from "./server/GG_WS_MESSAGE";
|
|
10
|
+
|
|
11
|
+
// Core
|
|
12
|
+
export * from "./socket/GGSocket";
|
|
13
|
+
|
|
14
|
+
// API Schema
|
|
15
|
+
export * from "./schema/webSocketSchema";
|
|
16
|
+
export * from "./schema/GGWebSocketSchema";
|
|
17
|
+
export * from "./schema/GGWebSocketMiddleware";
|
|
18
|
+
|
|
19
|
+
// Server
|
|
20
|
+
export * from "./server/GGSocketServer";
|
|
21
|
+
export * from "./server/GGWebSocketSchema.startServer";
|
|
22
|
+
|
|
23
|
+
// Client
|
|
24
|
+
export * from "./client/GGSocketClient";
|
|
25
|
+
export * from "./client/GGSocketPool";
|
|
26
|
+
|
|
27
|
+
// Extensions
|
|
28
|
+
import "./server/GGWebSocketSchema.startServer";
|