@opensumi/ide-connection 3.1.2-next-1718701452.0 → 3.1.2-next-1718769324.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,5 +1,5 @@
1
1
  import { EventEmitter } from '@opensumi/events';
2
- import { DisposableStore, EventQueue, StateTracer, randomString } from '@opensumi/ide-core-common';
2
+ import { DisposableStore, EventQueue, randomString } from '@opensumi/ide-core-common';
3
3
 
4
4
  import { ChannelMessage, ErrorMessageCode } from './channel/types';
5
5
  import { IConnectionShape } from './connection/types';
@@ -15,6 +15,82 @@ export interface IWSChannelCreateOptions {
15
15
  logger?: ILogger;
16
16
 
17
17
  ensureServerReady?: boolean;
18
+ deliveryTimeout?: number;
19
+ }
20
+
21
+ enum MessageDeliveryState {
22
+ ReSend,
23
+ Sended,
24
+ Success,
25
+ Failed,
26
+ }
27
+
28
+ class StateTracer {
29
+ private map = new Map<string, MessageDeliveryState>();
30
+
31
+ protected deliveryTimeout = 500;
32
+ protected timerMap = new Map<string, NodeJS.Timeout>();
33
+
34
+ setDeliveryTimeout(timeout: number) {
35
+ this.deliveryTimeout = timeout;
36
+ }
37
+
38
+ protected set(traceId: string, state: MessageDeliveryState) {
39
+ this.map.set(traceId, state);
40
+ }
41
+
42
+ get(traceId: string) {
43
+ return this.map.get(traceId);
44
+ }
45
+
46
+ success(traceId: string) {
47
+ this.map.set(traceId, MessageDeliveryState.Success);
48
+ const timer = this.timerMap.get(traceId);
49
+ if (timer) {
50
+ clearTimeout(timer);
51
+ }
52
+ }
53
+
54
+ dispose() {
55
+ this.timerMap.forEach((timer) => {
56
+ clearTimeout(timer);
57
+ });
58
+ }
59
+
60
+ stop(traceId: string) {
61
+ const timer = this.timerMap.get(traceId);
62
+ if (timer) {
63
+ clearTimeout(timer);
64
+ }
65
+ }
66
+
67
+ send(
68
+ traceId: string,
69
+ options: {
70
+ whenRetry: () => void;
71
+ },
72
+ ) {
73
+ this.set(traceId, MessageDeliveryState.Sended);
74
+ this.guard(traceId, options);
75
+ }
76
+
77
+ guard(
78
+ traceId: string,
79
+ options: {
80
+ whenRetry: () => void;
81
+ },
82
+ ) {
83
+ const timer = this.timerMap.get(traceId);
84
+ if (timer) {
85
+ clearTimeout(timer);
86
+ }
87
+
88
+ const newTimer = setTimeout(() => {
89
+ this.set(traceId, MessageDeliveryState.ReSend);
90
+ options.whenRetry();
91
+ }, this.deliveryTimeout);
92
+ this.timerMap.set(traceId, newTimer);
93
+ }
18
94
  }
19
95
 
20
96
  export class WSChannel {
@@ -35,6 +111,8 @@ export class WSChannel {
35
111
  protected _isServerReady = false;
36
112
  protected _ensureServerReady: boolean | undefined;
37
113
 
114
+ protected stateTracer = new StateTracer();
115
+
38
116
  public id: string;
39
117
 
40
118
  public channelPath: string;
@@ -53,6 +131,9 @@ export class WSChannel {
53
131
  }
54
132
 
55
133
  this._ensureServerReady = Boolean(ensureServerReady);
134
+ if (options.deliveryTimeout) {
135
+ this.stateTracer.setDeliveryTimeout(options.deliveryTimeout);
136
+ }
56
137
 
57
138
  this._disposables.add(this.emitter.on('binary', (data) => this.onBinaryQueue.push(data)));
58
139
  }
@@ -68,6 +149,30 @@ export class WSChannel {
68
149
  this.connection.send(data);
69
150
  }
70
151
 
152
+ /**
153
+ * @param traceId 一个 connection token 用于在全链路中追踪一个消息的生命周期,防止消息未发送或者重复发送
154
+ */
155
+ protected ensureMessageDeliveried(data: ChannelMessage, traceId = randomString(16)) {
156
+ const state = this.stateTracer.get(traceId);
157
+ if (state && state >= MessageDeliveryState.Sended) {
158
+ this.logger.error(`message already send already success or in progress, traceId: ${traceId}, state: ${state}`);
159
+ return;
160
+ }
161
+
162
+ data.traceId = traceId;
163
+ this.connection.send(data);
164
+
165
+ this.stateTracer.send(traceId, {
166
+ whenRetry: () => {
167
+ if (this._isServerReady) {
168
+ this.stateTracer.stop(traceId);
169
+ return;
170
+ }
171
+ this.ensureMessageDeliveried(data, traceId);
172
+ },
173
+ });
174
+ }
175
+
71
176
  onMessage(cb: (data: string) => any) {
72
177
  return this.emitter.on('message', cb);
73
178
  }
@@ -106,11 +211,11 @@ export class WSChannel {
106
211
  dispatch(msg: ChannelMessage) {
107
212
  switch (msg.kind) {
108
213
  case 'server-ready':
109
- this.stateTracer.fulfill(msg.token);
110
- this.resume();
111
- if (this.timer) {
112
- clearTimeout(this.timer);
214
+ if (msg.traceId) {
215
+ this.stateTracer.success(msg.traceId);
113
216
  }
217
+
218
+ this.resume();
114
219
  this.emitter.emit('open', msg.id);
115
220
  break;
116
221
  case 'data':
@@ -136,56 +241,24 @@ export class WSChannel {
136
241
  }
137
242
  }
138
243
 
139
- stateTracer = this._disposables.add(new StateTracer());
140
-
141
- /**
142
- * @param connectionToken 一个 connection token 用于在全链路中追踪一个 channel 的生命周期,防止 channel 被重复打开
143
- */
144
- open(path: string, clientId: string, connectionToken = randomString(16)) {
244
+ open(path: string, clientId: string) {
145
245
  this.channelPath = path;
146
246
  this.clientId = clientId;
147
247
 
148
248
  this.LOG_TAG = `[WSChannel id=${this.id} path=${path}]`;
149
249
 
150
- if (this.stateTracer.has(connectionToken)) {
151
- this.logger.warn(
152
- `channel already opened or in progress, path: ${path}, clientId: ${clientId}, connectionToken: ${connectionToken}`,
153
- );
154
- return;
155
- }
156
-
157
- this.stateTracer.record(connectionToken);
158
-
159
- this.connection.send({
250
+ const msg = {
160
251
  kind: 'open',
161
252
  id: this.id,
162
253
  path,
163
254
  clientId,
164
- connectionToken,
165
- });
255
+ } as ChannelMessage;
166
256
 
167
257
  if (this._ensureServerReady) {
168
- this.ensureOpenSend(path, clientId, connectionToken);
258
+ this.ensureMessageDeliveried(msg);
259
+ } else {
260
+ this.connection.send(msg);
169
261
  }
170
-
171
- return connectionToken;
172
- }
173
-
174
- protected timer: NodeJS.Timeout;
175
- /**
176
- * 启动定时器,确保 server-ready 消息在一定时间内到达
177
- */
178
- protected ensureOpenSend(path: string, clientId: string, connectionToken: string) {
179
- if (this.timer) {
180
- clearTimeout(this.timer);
181
- }
182
- this.timer = setTimeout(() => {
183
- if (this._isServerReady) {
184
- return;
185
- }
186
- this.stateTracer.delete(connectionToken);
187
- this.open(path, clientId, connectionToken);
188
- }, 500);
189
262
  }
190
263
 
191
264
  send(content: string) {
@@ -235,9 +308,7 @@ export class WSChannel {
235
308
  }
236
309
 
237
310
  dispose() {
238
- if (this.timer) {
239
- clearTimeout(this.timer);
240
- }
311
+ this.stateTracer.dispose();
241
312
  this.sendQueue = [];
242
313
  this._disposables.dispose();
243
314
  }
@@ -266,11 +337,11 @@ export class WSServerChannel extends WSChannel {
266
337
  this.clientId = options.clientId;
267
338
  }
268
339
 
269
- serverReady(token: string) {
340
+ serverReady(traceId: string) {
270
341
  this.connection.send({
271
342
  kind: 'server-ready',
272
343
  id: this.id,
273
- token,
344
+ traceId,
274
345
  });
275
346
  }
276
347