@opensumi/ide-connection 3.7.2-next-1740448398.0 → 3.7.2-next-1741226843.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.
Files changed (42) hide show
  1. package/lib/common/buffers/buffers.d.ts +0 -3
  2. package/lib/common/buffers/buffers.d.ts.map +1 -1
  3. package/lib/common/buffers/buffers.js +1 -32
  4. package/lib/common/buffers/buffers.js.map +1 -1
  5. package/lib/common/connection/drivers/frame-decoder.d.ts +14 -9
  6. package/lib/common/connection/drivers/frame-decoder.d.ts.map +1 -1
  7. package/lib/common/connection/drivers/frame-decoder.js +39 -95
  8. package/lib/common/connection/drivers/frame-decoder.js.map +1 -1
  9. package/lib/common/connection/drivers/reconnecting-websocket.d.ts +2 -8
  10. package/lib/common/connection/drivers/reconnecting-websocket.d.ts.map +1 -1
  11. package/lib/common/connection/drivers/reconnecting-websocket.js +26 -56
  12. package/lib/common/connection/drivers/reconnecting-websocket.js.map +1 -1
  13. package/lib/common/connection/drivers/stream.d.ts.map +1 -1
  14. package/lib/common/connection/drivers/stream.js +4 -11
  15. package/lib/common/connection/drivers/stream.js.map +1 -1
  16. package/lib/common/connection/drivers/ws-websocket.d.ts +1 -8
  17. package/lib/common/connection/drivers/ws-websocket.d.ts.map +1 -1
  18. package/lib/common/connection/drivers/ws-websocket.js +7 -77
  19. package/lib/common/connection/drivers/ws-websocket.js.map +1 -1
  20. package/lib/common/constants.d.ts +0 -4
  21. package/lib/common/constants.d.ts.map +1 -1
  22. package/lib/common/constants.js +1 -5
  23. package/lib/common/constants.js.map +1 -1
  24. package/lib/common/fury-extends/one-of.d.ts.map +1 -1
  25. package/lib/common/fury-extends/one-of.js +0 -3
  26. package/lib/common/fury-extends/one-of.js.map +1 -1
  27. package/lib/common/rpc/connection.d.ts.map +1 -1
  28. package/lib/common/rpc/connection.js +0 -3
  29. package/lib/common/rpc/connection.js.map +1 -1
  30. package/lib/node/common-channel-handler.d.ts.map +1 -1
  31. package/lib/node/common-channel-handler.js +2 -1
  32. package/lib/node/common-channel-handler.js.map +1 -1
  33. package/package.json +6 -6
  34. package/src/common/buffers/buffers.ts +0 -40
  35. package/src/common/connection/drivers/frame-decoder.ts +43 -103
  36. package/src/common/connection/drivers/reconnecting-websocket.ts +26 -66
  37. package/src/common/connection/drivers/stream.ts +4 -11
  38. package/src/common/connection/drivers/ws-websocket.ts +8 -93
  39. package/src/common/constants.ts +0 -5
  40. package/src/common/fury-extends/one-of.ts +0 -3
  41. package/src/common/rpc/connection.ts +0 -6
  42. package/src/node/common-channel-handler.ts +2 -1
@@ -1,7 +1,6 @@
1
- /* eslint-disable no-console */
2
1
  import { BinaryWriter } from '@furyjs/fury/dist/lib/writer';
3
2
 
4
- import { MaybeNull, readUInt32LE } from '@opensumi/ide-core-common';
3
+ import { Emitter, readUInt32LE } from '@opensumi/ide-core-common';
5
4
 
6
5
  import { Buffers } from '../../buffers/buffers';
7
6
 
@@ -10,40 +9,30 @@ import { Buffers } from '../../buffers/buffers';
10
9
  */
11
10
  export const indicator = new Uint8Array([0x0d, 0x0a, 0x0d, 0x0a]);
12
11
 
13
- /**
14
- * The number of bytes in the length field.
15
- *
16
- * How many bytes are used to represent data length.
17
- *
18
- * For example, if the length field is 4 bytes, then the maximum length of the data is 2^32 = 4GB
19
- */
20
- const lengthFieldLength = 4;
21
-
22
12
  /**
23
13
  * sticky packet unpacking problems are generally problems at the transport layer.
24
14
  * we use a length field to represent the length of the data, and then read the data according to the length
25
15
  */
26
16
  export class LengthFieldBasedFrameDecoder {
27
- private static readonly MAX_ITERATIONS = 50;
28
-
29
- private _onDataListener: MaybeNull<(data: Uint8Array) => void>;
30
- onData(listener: (data: Uint8Array) => void) {
31
- this._onDataListener = listener;
32
- return {
33
- dispose: () => {
34
- this._onDataListener = null;
35
- },
36
- };
37
- }
17
+ protected dataEmitter = new Emitter<Uint8Array>();
18
+ onData = this.dataEmitter.event;
38
19
 
39
20
  protected buffers = new Buffers();
40
21
  protected cursor = this.buffers.cursor();
41
- private processingPromise: Promise<void> | null = null;
42
22
 
43
23
  protected contentLength = -1;
44
24
 
45
25
  protected state = 0;
46
26
 
27
+ /**
28
+ * The number of bytes in the length field.
29
+ *
30
+ * How many bytes are used to represent data length.
31
+ *
32
+ * For example, if the length field is 4 bytes, then the maximum length of the data is 2^32 = 4GB
33
+ */
34
+ lengthFieldLength = 4;
35
+
47
36
  reset() {
48
37
  this.contentLength = -1;
49
38
  this.state = 0;
@@ -52,81 +41,38 @@ export class LengthFieldBasedFrameDecoder {
52
41
 
53
42
  push(chunk: Uint8Array): void {
54
43
  this.buffers.push(chunk);
44
+ let done = false;
55
45
 
56
- // 确保同一时间只有一个处理过程
57
- if (!this.processingPromise) {
58
- this.processingPromise = this.processBuffers().finally(() => {
59
- this.processingPromise = null;
60
- });
46
+ while (!done) {
47
+ done = this.readFrame();
61
48
  }
62
49
  }
63
50
 
64
- private async processBuffers(): Promise<void> {
65
- let iterations = 0;
66
- let hasMoreData = false;
67
-
68
- do {
69
- hasMoreData = false;
70
- while (iterations < LengthFieldBasedFrameDecoder.MAX_ITERATIONS) {
71
- if (this.buffers.byteLength === 0) {
72
- break;
73
- }
74
-
75
- const result = await this.readFrame();
76
- if (result === true) {
77
- break;
78
- }
79
-
80
- iterations++;
81
- if (iterations % 10 === 0) {
82
- await new Promise((resolve) => setTimeout(resolve, 0));
83
- }
84
- }
85
-
86
- // 检查剩余数据
87
- if (this.buffers.byteLength > 0) {
88
- hasMoreData = true;
89
- // 异步继续处理,避免阻塞
90
- await new Promise((resolve) => setImmediate(resolve));
91
- iterations = 0; // 重置迭代计数器
92
- }
93
- } while (hasMoreData);
94
- }
95
-
96
- protected async readFrame(): Promise<boolean> {
97
- try {
98
- const found = this.readLengthField();
99
- if (!found) {
100
- return true;
101
- }
51
+ protected readFrame(): boolean {
52
+ const found = this.readLengthField();
102
53
 
54
+ if (found) {
103
55
  const start = this.cursor.offset;
104
56
  const end = start + this.contentLength;
105
57
 
106
- if (end > this.buffers.byteLength) {
107
- return true;
108
- }
109
-
110
58
  const binary = this.buffers.slice(start, end);
111
59
 
112
- // 立即清理已处理的数据
113
- this.buffers.splice(0, end);
114
- this.reset();
60
+ this.dataEmitter.fire(binary);
115
61
 
116
- if (this._onDataListener) {
117
- try {
118
- await Promise.resolve().then(() => this._onDataListener?.(binary));
119
- } catch (error) {
120
- console.error('[Frame Decoder] Error in data listener:', error);
121
- }
62
+ if (this.buffers.byteLength > end) {
63
+ this.contentLength = -1;
64
+ this.state = 0;
65
+ this.cursor.moveTo(end);
66
+ // has more data, continue to parse
67
+ return false;
122
68
  }
123
69
 
124
- return false;
125
- } catch (error) {
126
- console.error('[Frame Decoder] Error processing frame:', error);
70
+ // delete used buffers
71
+ this.buffers.splice(0, end);
127
72
  this.reset();
128
- return true;
129
73
  }
74
+
75
+ return true;
130
76
  }
131
77
 
132
78
  protected readLengthField() {
@@ -147,13 +93,13 @@ export class LengthFieldBasedFrameDecoder {
147
93
  }
148
94
 
149
95
  if (this.contentLength === -1) {
150
- if (this.cursor.offset + lengthFieldLength > bufferLength) {
96
+ if (this.cursor.offset + this.lengthFieldLength > bufferLength) {
151
97
  // Not enough data yet, wait for more data
152
98
  return false;
153
99
  }
154
100
 
155
101
  // read the content length
156
- const buf = this.cursor.read4();
102
+ const buf = this.cursor.read(this.lengthFieldLength);
157
103
  // fury writer use little endian
158
104
  this.contentLength = readUInt32LE(buf, 0);
159
105
  }
@@ -172,12 +118,12 @@ export class LengthFieldBasedFrameDecoder {
172
118
  let result = iter.next();
173
119
  while (!result.done) {
174
120
  switch (result.value) {
175
- case 0x0d: // \r
121
+ case 0x0d:
176
122
  switch (this.state) {
177
123
  case 0:
178
124
  this.state = 1;
179
125
  break;
180
- case 2: // 第二个 \r
126
+ case 2:
181
127
  this.state = 3;
182
128
  break;
183
129
  default:
@@ -185,12 +131,12 @@ export class LengthFieldBasedFrameDecoder {
185
131
  break;
186
132
  }
187
133
  break;
188
- case 0x0a: // \n
134
+ case 0x0a:
189
135
  switch (this.state) {
190
136
  case 1:
191
137
  this.state = 2;
192
138
  break;
193
- case 3: // 第二个 \n
139
+ case 3:
194
140
  this.state = 4;
195
141
  iter.return();
196
142
  break;
@@ -208,23 +154,17 @@ export class LengthFieldBasedFrameDecoder {
208
154
  }
209
155
 
210
156
  dispose() {
211
- this._onDataListener = null;
157
+ this.dataEmitter.dispose();
212
158
  this.buffers.dispose();
213
- this.reset();
214
159
  }
215
160
 
161
+ static writer = BinaryWriter({});
162
+
216
163
  static construct(content: Uint8Array) {
217
- // 每次都创建新的 writer,避免所有权问题
218
- const writer = BinaryWriter({});
219
-
220
- try {
221
- writer.buffer(indicator);
222
- writer.uint32(content.byteLength);
223
- writer.buffer(content);
224
- return writer;
225
- } catch (error) {
226
- console.warn('[Frame Decoder] Error constructing frame:', error);
227
- throw error;
228
- }
164
+ LengthFieldBasedFrameDecoder.writer.reset();
165
+ LengthFieldBasedFrameDecoder.writer.buffer(indicator);
166
+ LengthFieldBasedFrameDecoder.writer.uint32(content.byteLength);
167
+ LengthFieldBasedFrameDecoder.writer.buffer(content);
168
+ return LengthFieldBasedFrameDecoder.writer.dump();
229
169
  }
230
170
  }
@@ -1,74 +1,20 @@
1
- /* eslint-disable no-console */
2
1
  import { IDisposable } from '@opensumi/ide-core-common';
3
2
  import ReconnectingWebSocket, {
4
3
  Options as ReconnectingWebSocketOptions,
5
4
  UrlProvider,
6
5
  } from '@opensumi/reconnecting-websocket';
7
6
 
8
- import { chunkSize } from '../../constants';
9
-
10
7
  import { BaseConnection } from './base';
11
- import { LengthFieldBasedFrameDecoder } from './frame-decoder';
12
8
 
13
9
  import type { ErrorEvent } from '@opensumi/reconnecting-websocket';
14
10
 
15
11
  export class ReconnectingWebSocketConnection extends BaseConnection<Uint8Array> {
16
- protected decoder = new LengthFieldBasedFrameDecoder();
17
- private sendQueue: Array<{ data: Uint8Array; resolve: () => void; reject: (error: Error) => void }> = [];
18
- private sending = false;
19
-
20
- protected constructor(private socket: ReconnectingWebSocket) {
12
+ constructor(private socket: ReconnectingWebSocket) {
21
13
  super();
22
-
23
- if (socket.binaryType === 'arraybuffer') {
24
- this.socket.addEventListener('message', this.arrayBufferHandler);
25
- } else if (socket.binaryType === 'blob') {
26
- throw new Error('blob is not implemented');
27
- }
28
14
  }
29
15
 
30
- private async processSendQueue() {
31
- if (this.sending) {
32
- return;
33
- }
34
- this.sending = true;
35
-
36
- while (this.sendQueue.length > 0) {
37
- const { data, resolve, reject } = this.sendQueue[0];
38
- let handle: { get: () => Uint8Array; dispose: () => void } | null = null;
39
-
40
- try {
41
- handle = LengthFieldBasedFrameDecoder.construct(data).dumpAndOwn();
42
- const packet = handle.get();
43
-
44
- for (let i = 0; i < packet.byteLength; i += chunkSize) {
45
- await new Promise<void>((resolve) => {
46
- const chunk = packet.subarray(i, Math.min(i + chunkSize, packet.byteLength));
47
- this.socket.send(chunk);
48
- resolve();
49
- });
50
- }
51
-
52
- resolve();
53
- } catch (error) {
54
- console.error('[ReconnectingWebSocket] Error sending data:', error);
55
- reject(error);
56
- } finally {
57
- if (handle) {
58
- handle.dispose();
59
- }
60
- }
61
- this.sendQueue.shift();
62
- }
63
-
64
- this.sending = false;
65
- }
66
-
67
- send(data: Uint8Array): Promise<void> {
68
- return new Promise((resolve, reject) => {
69
- this.sendQueue.push({ data, resolve, reject });
70
- this.processSendQueue();
71
- });
16
+ send(data: Uint8Array): void {
17
+ this.socket.send(data);
72
18
  }
73
19
 
74
20
  isOpen(): boolean {
@@ -83,8 +29,29 @@ export class ReconnectingWebSocketConnection extends BaseConnection<Uint8Array>
83
29
  },
84
30
  };
85
31
  }
32
+
86
33
  onMessage(cb: (data: Uint8Array) => void): IDisposable {
87
- return this.decoder.onData(cb);
34
+ const handler = (e: MessageEvent) => {
35
+ let buffer: Promise<ArrayBuffer>;
36
+ if (e.data instanceof Blob) {
37
+ buffer = e.data.arrayBuffer();
38
+ } else if (e.data instanceof ArrayBuffer) {
39
+ buffer = Promise.resolve(e.data);
40
+ } else if (e.data?.constructor?.name === 'Buffer') {
41
+ // Compatibility with nodejs Buffer in test environment
42
+ buffer = Promise.resolve(e.data);
43
+ } else {
44
+ throw new Error('unknown message type, expect Blob or ArrayBuffer, received: ' + typeof e.data);
45
+ }
46
+ buffer.then((v) => cb(new Uint8Array(v, 0, v.byteLength)));
47
+ };
48
+
49
+ this.socket.addEventListener('message', handler);
50
+ return {
51
+ dispose: () => {
52
+ this.socket.removeEventListener('message', handler);
53
+ },
54
+ };
88
55
  }
89
56
  onceClose(cb: (code?: number, reason?: string) => void): IDisposable {
90
57
  const disposable = this.onClose(wrapper);
@@ -124,15 +91,8 @@ export class ReconnectingWebSocketConnection extends BaseConnection<Uint8Array>
124
91
  };
125
92
  }
126
93
 
127
- private arrayBufferHandler = (e: MessageEvent<ArrayBuffer>) => {
128
- const buffer: ArrayBuffer = e.data;
129
- this.decoder.push(new Uint8Array(buffer, 0, buffer.byteLength));
130
- };
131
-
132
94
  dispose(): void {
133
- this.socket.removeEventListener('message', this.arrayBufferHandler);
134
- this.sendQueue = [];
135
- this.sending = false;
95
+ // do nothing
136
96
  }
137
97
 
138
98
  static forURL(url: UrlProvider, protocols?: string | string[], options?: ReconnectingWebSocketOptions) {
@@ -1,4 +1,3 @@
1
- /* eslint-disable no-console */
2
1
  import { IDisposable } from '@opensumi/ide-core-common';
3
2
 
4
3
  import { BaseConnection } from './base';
@@ -22,16 +21,10 @@ export class StreamConnection extends BaseConnection<Uint8Array> {
22
21
  }
23
22
 
24
23
  send(data: Uint8Array): void {
25
- const handle = LengthFieldBasedFrameDecoder.construct(data).dumpAndOwn();
26
- try {
27
- this.writable.write(handle.get(), (error) => {
28
- if (error) {
29
- console.error('Failed to write data:', error);
30
- }
31
- });
32
- } finally {
33
- handle.dispose();
34
- }
24
+ const result = LengthFieldBasedFrameDecoder.construct(data);
25
+ this.writable.write(result, () => {
26
+ // TODO: logger error
27
+ });
35
28
  }
36
29
 
37
30
  onMessage(cb: (data: Uint8Array) => void): IDisposable {
@@ -1,102 +1,24 @@
1
- /* eslint-disable no-console */
2
1
  import { IDisposable } from '@opensumi/ide-core-common';
3
2
 
4
- import { chunkSize } from '../../constants';
5
-
6
3
  import { BaseConnection } from './base';
7
- import { LengthFieldBasedFrameDecoder } from './frame-decoder';
8
4
 
9
5
  import type WebSocket from 'ws';
10
6
 
11
- interface SendQueueItem {
12
- data: Uint8Array;
13
- resolve: () => void;
14
- reject: (error: Error) => void;
15
- }
16
-
17
7
  export class WSWebSocketConnection extends BaseConnection<Uint8Array> {
18
- protected decoder = new LengthFieldBasedFrameDecoder();
19
- private static readonly MAX_QUEUE_SIZE = 1000; // 限制队列长度
20
-
21
- private sendQueue: SendQueueItem[] = [];
22
- private pendingSize = 0;
23
- private sending = false;
24
-
25
8
  constructor(public socket: WebSocket) {
26
9
  super();
27
- this.socket.on('message', (data: Buffer) => {
28
- this.decoder.push(data);
29
- });
30
10
  }
31
-
32
- private async processSendQueue() {
33
- if (this.sending) {
34
- return;
35
- }
36
- this.sending = true;
37
-
38
- while (this.sendQueue.length > 0) {
39
- const { data, resolve, reject } = this.sendQueue[0];
40
- let handle: { get: () => Uint8Array; dispose: () => void } | null = null;
41
-
42
- try {
43
- handle = LengthFieldBasedFrameDecoder.construct(data).dumpAndOwn();
44
- const packet = handle.get();
45
-
46
- for (let i = 0; i < packet.byteLength; i += chunkSize) {
47
- if (!this.isOpen()) {
48
- throw new Error('Connection closed while sending');
49
- }
50
-
51
- await new Promise<void>((resolve, reject) => {
52
- const chunk = packet.subarray(i, Math.min(i + chunkSize, packet.byteLength));
53
- this.socket.send(chunk, { binary: true }, (error?: Error) => {
54
- if (error) {
55
- reject(error);
56
- } else {
57
- resolve();
58
- }
59
- });
60
- });
61
- }
62
-
63
- resolve();
64
- } catch (error) {
65
- reject(error instanceof Error ? error : new Error(String(error)));
66
- } finally {
67
- if (handle) {
68
- try {
69
- handle.dispose();
70
- } catch (error) {
71
- console.warn('[WSWebSocket] Error disposing handle:', error);
72
- }
73
- }
74
- this.pendingSize -= this.sendQueue[0].data.byteLength;
75
- this.sendQueue.shift();
76
- }
77
- }
78
-
79
- this.sending = false;
80
- }
81
-
82
- send(data: Uint8Array): Promise<void> {
83
- return new Promise<void>((resolve, reject) => {
84
- // 检查队列大小限制
85
- if (this.sendQueue.length >= WSWebSocketConnection.MAX_QUEUE_SIZE) {
86
- reject(new Error('Send queue full'));
87
- return;
88
- }
89
-
90
- this.pendingSize += data.byteLength;
91
- this.sendQueue.push({ data, resolve, reject });
92
- this.processSendQueue().catch((error) => {
93
- console.error('[WSWebSocket] Error processing queue:', error);
94
- });
95
- });
11
+ send(data: Uint8Array): void {
12
+ this.socket.send(data);
96
13
  }
97
14
 
98
15
  onMessage(cb: (data: Uint8Array) => void): IDisposable {
99
- return this.decoder.onData(cb);
16
+ this.socket.on('message', cb);
17
+ return {
18
+ dispose: () => {
19
+ this.socket.off('message', cb);
20
+ },
21
+ };
100
22
  }
101
23
  onceClose(cb: () => void): IDisposable {
102
24
  this.socket.once('close', cb);
@@ -113,12 +35,5 @@ export class WSWebSocketConnection extends BaseConnection<Uint8Array> {
113
35
 
114
36
  dispose(): void {
115
37
  this.socket.removeAllListeners();
116
- // 拒绝所有待发送的消息
117
- while (this.sendQueue.length > 0) {
118
- const { reject } = this.sendQueue.shift()!;
119
- reject(new Error('Connection disposed'));
120
- }
121
- this.pendingSize = 0;
122
- this.sending = false;
123
38
  }
124
39
  }
@@ -1,6 +1 @@
1
1
  export const METHOD_NOT_REGISTERED = '$$METHOD_NOT_REGISTERED';
2
-
3
- /**
4
- * 分片大小, 1MB
5
- */
6
- export const chunkSize = 1 * 1024 * 1024;
@@ -68,9 +68,6 @@ export const oneOf = (
68
68
  case 7:
69
69
  v = serializers[7].read();
70
70
  break;
71
- default: {
72
- throw new Error('unknown index: ' + idx);
73
- }
74
71
  }
75
72
 
76
73
  v.kind = kinds[idx];
@@ -202,12 +202,6 @@ export class SumiConnection implements IDisposable {
202
202
  const opType = message.kind;
203
203
  const requestId = message.requestId;
204
204
 
205
- if (opType === OperationType.Error) {
206
- this.logger.warn(
207
- `[${message.requestId}] Error received from server method ${message.method}: ${message.error}`,
208
- );
209
- }
210
-
211
205
  switch (opType) {
212
206
  case OperationType.Error:
213
207
  case OperationType.Response: {
@@ -42,7 +42,8 @@ export class CommonChannelHandler extends BaseCommonChannelHandler implements We
42
42
  ...this.options.wsServerOptions,
43
43
  });
44
44
  this.wsServer.on('connection', (connection: WebSocket) => {
45
- this.receiveConnection(new WSWebSocketConnection(connection));
45
+ const wsConnection = new WSWebSocketConnection(connection);
46
+ this.receiveConnection(wsConnection);
46
47
  });
47
48
  }
48
49