@opensumi/ide-connection 3.0.2-next-1715689237.0 → 3.0.2-next-1715702831.0

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.
@@ -1,10 +1,16 @@
1
1
  import { Type } from '@furyjs/fury';
2
2
 
3
3
  import { EventEmitter } from '@opensumi/events';
4
- import { DisposableCollection, DisposableStore, EventQueue } from '@opensumi/ide-core-common';
4
+ import {
5
+ DisposableCollection,
6
+ DisposableStore,
7
+ EventQueue,
8
+ StateTracer,
9
+ randomString,
10
+ } from '@opensumi/ide-core-common';
5
11
 
6
12
  import { IConnectionShape } from './connection/types';
7
- import { oneOf7 } from './fury-extends/one-of';
13
+ import { oneOf } from './fury-extends/one-of';
8
14
  import { ISumiConnectionOptions, SumiConnection } from './rpc/connection';
9
15
  import { ILogger } from './types';
10
16
 
@@ -26,27 +32,6 @@ export interface PongMessage {
26
32
  clientId: string;
27
33
  }
28
34
 
29
- /**
30
- * `open` message is used to open a new channel.
31
- * `path` is used to identify which handler should be used to handle the channel.
32
- * `clientId` is used to identify the client.
33
- */
34
- export interface OpenMessage {
35
- kind: 'open';
36
- id: string;
37
- path: string;
38
- clientId: string;
39
- }
40
-
41
- /**
42
- * when server receive a `open` message, it should reply a `server-ready` message.
43
- * this is indicate that the channel is ready to use.
44
- */
45
- export interface ServerReadyMessage {
46
- kind: 'server-ready';
47
- id: string;
48
- }
49
-
50
35
  /**
51
36
  * `data` message indicate that the channel has received some data.
52
37
  * the `content` field is the data, it should be a string.
@@ -70,15 +55,6 @@ export interface CloseMessage {
70
55
  reason: string;
71
56
  }
72
57
 
73
- export type ChannelMessage =
74
- | PingMessage
75
- | PongMessage
76
- | OpenMessage
77
- | ServerReadyMessage
78
- | DataMessage
79
- | BinaryMessage
80
- | CloseMessage;
81
-
82
58
  export interface IWSChannelCreateOptions {
83
59
  /**
84
60
  * every channel's unique id, it only used in client to server architecture.
@@ -172,6 +148,14 @@ export class WSChannel {
172
148
  this._isServerReady = false;
173
149
  }
174
150
 
151
+ onServerReady(cb: () => void) {
152
+ if (this._isServerReady) {
153
+ cb();
154
+ return;
155
+ }
156
+ return this.emitter.on('open', cb);
157
+ }
158
+
175
159
  resume() {
176
160
  this._isServerReady = true;
177
161
  if (this.sendQueue) {
@@ -185,6 +169,7 @@ export class WSChannel {
185
169
  dispatch(msg: ChannelMessage) {
186
170
  switch (msg.kind) {
187
171
  case 'server-ready':
172
+ this.stateTracer.fulfill(msg.token);
188
173
  this.resume();
189
174
  if (this.timer) {
190
175
  clearTimeout(this.timer);
@@ -200,27 +185,45 @@ export class WSChannel {
200
185
  }
201
186
  }
202
187
 
203
- open(path: string, clientId: string) {
188
+ stateTracer = this._disposables.add(new StateTracer());
189
+
190
+ /**
191
+ * @param connectionToken 一个 connection token 用于在全链路中追踪一个 channel 的生命周期,防止 channel 被重复打开
192
+ */
193
+ open(path: string, clientId: string, connectionToken = randomString(16)) {
204
194
  this.channelPath = path;
195
+
196
+ if (this.stateTracer.has(connectionToken)) {
197
+ this.logger.warn(
198
+ `channel already opened or in progress, path: ${path}, clientId: ${clientId}, connectionToken: ${connectionToken}`,
199
+ );
200
+ return;
201
+ }
202
+
203
+ this.stateTracer.record(connectionToken);
204
+
205
205
  this.connection.send(
206
206
  stringify({
207
207
  kind: 'open',
208
208
  id: this.id,
209
209
  path,
210
210
  clientId,
211
+ connectionToken,
211
212
  }),
212
213
  );
213
214
 
214
215
  if (this._ensureServerReady) {
215
- this.ensureOpenSend(path, clientId);
216
+ this.ensureOpenSend(path, clientId, connectionToken);
216
217
  }
218
+
219
+ return connectionToken;
217
220
  }
218
221
 
219
222
  protected timer: NodeJS.Timeout;
220
223
  /**
221
224
  * 启动定时器,确保 server-ready 消息在一定时间内到达
222
225
  */
223
- protected ensureOpenSend(path: string, clientId: string) {
226
+ protected ensureOpenSend(path: string, clientId: string, connectionToken: string) {
224
227
  if (this.timer) {
225
228
  clearTimeout(this.timer);
226
229
  }
@@ -228,8 +231,8 @@ export class WSChannel {
228
231
  if (this._isServerReady) {
229
232
  return;
230
233
  }
231
-
232
- this.open(path, clientId);
234
+ this.stateTracer.delete(connectionToken);
235
+ this.open(path, clientId, connectionToken);
233
236
  }, 500);
234
237
  }
235
238
 
@@ -300,20 +303,45 @@ interface IWSServerChannelCreateOptions extends IWSChannelCreateOptions {
300
303
  * The server side channel, it will send a `server-ready` message after it receive a `open` message.
301
304
  */
302
305
  export class WSServerChannel extends WSChannel {
306
+ messageQueue: ChannelMessage[] = [];
307
+
303
308
  clientId: string;
304
309
  constructor(public connection: IConnectionShape<Uint8Array>, options: IWSServerChannelCreateOptions) {
305
310
  super(connection, options);
306
311
  this.clientId = options.clientId;
307
312
  }
308
- serverReady() {
313
+ serverReady(token: string) {
309
314
  this.connection.send(
310
315
  stringify({
311
316
  kind: 'server-ready',
312
317
  id: this.id,
318
+ token,
313
319
  }),
314
320
  );
315
321
  }
322
+
323
+ dispatch(msg: ChannelMessage) {
324
+ switch (msg.kind) {
325
+ case 'data':
326
+ this.emitter.emit('message', msg.content);
327
+ break;
328
+ case 'binary':
329
+ this.emitter.emit('binary', msg.binary);
330
+ break;
331
+ }
332
+ }
316
333
  }
334
+
335
+ export type ChannelMessage =
336
+ | PingMessage
337
+ | PongMessage
338
+ | OpenMessage
339
+ | ServerReadyMessage
340
+ | DataMessage
341
+ | BinaryMessage
342
+ | CloseMessage
343
+ | ErrorMessage;
344
+
317
345
  export const PingProtocol = Type.object('ping', {
318
346
  clientId: Type.string(),
319
347
  id: Type.string(),
@@ -324,14 +352,56 @@ export const PongProtocol = Type.object('pong', {
324
352
  id: Type.string(),
325
353
  });
326
354
 
355
+ /**
356
+ * `open` message is used to open a new channel.
357
+ * `path` is used to identify which handler should be used to handle the channel.
358
+ * `clientId` is used to identify the client.
359
+ */
360
+ export interface OpenMessage {
361
+ kind: 'open';
362
+ id: string;
363
+ path: string;
364
+ clientId: string;
365
+ connectionToken: string;
366
+ }
367
+
327
368
  export const OpenProtocol = Type.object('open', {
328
369
  clientId: Type.string(),
329
370
  id: Type.string(),
330
371
  path: Type.string(),
372
+ connectionToken: Type.string(),
331
373
  });
332
374
 
375
+ /**
376
+ * when server receive a `open` message, it should reply a `server-ready` message.
377
+ * this is indicate that the channel is ready to use.
378
+ */
379
+ export interface ServerReadyMessage {
380
+ kind: 'server-ready';
381
+ id: string;
382
+ token: string;
383
+ }
384
+
333
385
  export const ServerReadyProtocol = Type.object('server-ready', {
334
386
  id: Type.string(),
387
+ token: Type.string(),
388
+ });
389
+
390
+ export enum ErrorMessageCode {
391
+ ChannelNotFound = 1,
392
+ }
393
+
394
+ export interface ErrorMessage {
395
+ kind: 'error';
396
+ id: string;
397
+ code: ErrorMessageCode;
398
+ message: string;
399
+ }
400
+
401
+ export const ErrorProtocol = Type.object('error', {
402
+ id: Type.string(),
403
+ code: Type.uint16(),
404
+ message: Type.string(),
335
405
  });
336
406
 
337
407
  export const DataProtocol = Type.object('data', {
@@ -350,7 +420,7 @@ export const CloseProtocol = Type.object('close', {
350
420
  reason: Type.string(),
351
421
  });
352
422
 
353
- const serializer = oneOf7([
423
+ const serializer = oneOf([
354
424
  PingProtocol,
355
425
  PongProtocol,
356
426
  OpenProtocol,
@@ -358,6 +428,7 @@ const serializer = oneOf7([
358
428
  DataProtocol,
359
429
  BinaryProtocol,
360
430
  CloseProtocol,
431
+ ErrorProtocol,
361
432
  ]);
362
433
 
363
434
  export function stringify(obj: ChannelMessage): Uint8Array {
package/src/node/ws.ts CHANGED
@@ -107,6 +107,7 @@ export class WebSocketServerRoute {
107
107
 
108
108
  if (wsHandlerIndex === wsHandlerLength) {
109
109
  this.logger.error(`request.url ${request.url} mismatch!`);
110
+ socket.destroy();
110
111
  }
111
112
  });
112
113
  }