huaweicloud-devkit 1.0.2-next.2 → 1.0.2-next.21
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/.agents/plugins/marketplace.json +18 -18
- package/README.md +81 -51
- package/README.zh-CN.md +81 -49
- package/bin/setup.cjs +5 -3
- package/integrations/opencode/opencode.json +1 -4
- package/package.json +61 -44
- package/plugins/huaweicloud-core/.claude-plugin/plugin.json +4 -17
- package/plugins/huaweicloud-core/.codex-plugin/plugin.json +4 -17
- package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +4 -17
- package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +30 -0
- package/plugins/huaweicloud-core/.mcp.json +2 -5
- package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +4 -18
- package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +32 -9
- package/plugins/huaweicloud-core/openclaw.plugin.json +20 -0
- package/plugins/huaweicloud-core/safety/policy.json +75 -99
- package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +41 -13
- package/plugins/huaweicloud-core/skills/huawei-apig/SKILL.md +34 -34
- package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +18 -18
- package/plugins/huaweicloud-core/skills/huawei-cbr/SKILL.md +19 -19
- package/plugins/huaweicloud-core/skills/huawei-cce/SKILL.md +34 -32
- package/plugins/huaweicloud-core/skills/huawei-cloud-eye/SKILL.md +30 -30
- package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/SKILL.md +13 -12
- package/plugins/huaweicloud-core/skills/huawei-cloud-find-skills/scripts/search-skills.mjs +64 -17
- package/plugins/huaweicloud-core/skills/huawei-cts/SKILL.md +20 -20
- package/plugins/huaweicloud-core/skills/huawei-dds-dcs/SKILL.md +31 -31
- package/plugins/huaweicloud-core/skills/huawei-deployment/SKILL.md +21 -20
- package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +39 -30
- package/plugins/huaweicloud-core/skills/huawei-dew/references/csms-usage.md +8 -1
- package/plugins/huaweicloud-core/skills/huawei-dew/references/kms-usage.md +6 -0
- package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +39 -36
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/create-instance.md +15 -4
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/flavors.md +11 -9
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/troubleshooting.md +13 -13
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/SKILL.md +40 -39
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/apig-event-format.md +12 -10
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/create-function.md +21 -20
- package/plugins/huaweicloud-core/skills/huawei-functiongraph/references/triggers.md +19 -19
- package/plugins/huaweicloud-core/skills/huawei-gaussdb/SKILL.md +20 -20
- package/plugins/huaweicloud-core/skills/huawei-getting-started/SKILL.md +27 -23
- package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +27 -27
- package/plugins/huaweicloud-core/skills/huawei-iam/references/policy-examples.md +65 -39
- package/plugins/huaweicloud-core/skills/huawei-iam/references/quick-ref.md +9 -9
- package/plugins/huaweicloud-core/skills/huawei-modelarts/SKILL.md +25 -25
- package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +45 -44
- package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-create.md +19 -19
- package/plugins/huaweicloud-core/skills/huawei-obs/references/bucket-lifecycle.md +12 -7
- package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +13 -12
- package/plugins/huaweicloud-core/skills/huawei-obs/references/single-file-share.md +4 -4
- package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +63 -58
- package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +127 -42
- package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +26 -26
- package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +39 -39
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +13 -12
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/security-group.md +20 -19
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/subnet.md +15 -15
- package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +18 -18
- package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/SKILL.md +13 -13
- package/plugins/huaweicloud-core/skills/huaweicloud-api-and-sdk/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/SKILL.md +17 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-capability-discovery/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +27 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +56 -53
- package/plugins/huaweicloud-core/skills/huaweicloud-core/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-core/references/report-issue.md +6 -6
- package/plugins/huaweicloud-core/skills/huaweicloud-core/references/select.md +15 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-safety/SKILL.md +0 -1
- package/plugins/huaweicloud-core/skills/huaweicloud-safety/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/SKILL.md +33 -33
- package/plugins/huaweicloud-core/skills/huaweicloud-troubleshooting/agents/openai.yaml +3 -3
- package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +108 -5
- package/plugins/huaweicloud-core/src/auth/credentials.mjs +77 -4
- package/plugins/huaweicloud-core/src/auth/service.mjs +1 -1
- package/plugins/huaweicloud-core/src/data/icons-manifest.v1.json +3517 -0
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +25 -7
- package/plugins/huaweicloud-core/src/icon-library.mjs +171 -0
- package/plugins/huaweicloud-core/src/mcp-server.mjs +30 -3
- package/plugins/huaweicloud-core/src/proxy/proxy-agent.mjs +20 -102
- package/plugins/huaweicloud-core/src/proxy/proxy-config.mjs +11 -4
- package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +4 -1
- package/plugins/huaweicloud-core/src/safety-policy.mjs +122 -68
- package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +9 -9
- package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +24 -26
- package/plugins/huaweicloud-core/src/sandbox/sandbox-file-server.py +45 -0
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +510 -10
- package/plugins/huaweicloud-core/src/search-market.mjs +46 -12
- package/plugins/huaweicloud-core/src/setup-cli.mjs +1251 -181
- package/plugins/huaweicloud-core/src/tools.mjs +600 -122
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-exec-client.js +72 -38
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-fair-queue.js +10 -5
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-multiplexer.js +12 -15
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-packet.js +12 -16
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-terminal-channel.js +46 -31
- package/plugins/huaweicloud-core/src/ws-exec/hwlink-tunnel-channel.mjs +267 -0
- package/plugins/huaweicloud-core/src/ws-exec/index.js +3 -0
- package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +9 -5
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file contains code derived from hwlink.
|
|
3
|
+
*
|
|
4
|
+
* Source:
|
|
5
|
+
* https://gitcode.com/huawei-developers/hwlink
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the ISC License.
|
|
8
|
+
*/
|
|
9
|
+
|
|
1
10
|
import {
|
|
2
11
|
DEFAULT_TIMEOUT_MS,
|
|
3
12
|
WebSocketExecError,
|
|
@@ -93,9 +102,11 @@ function createHwlinkTerminal(options = {}) {
|
|
|
93
102
|
}
|
|
94
103
|
|
|
95
104
|
function handleClose() {
|
|
96
|
-
settleReady(
|
|
97
|
-
|
|
98
|
-
|
|
105
|
+
settleReady(
|
|
106
|
+
new WebSocketExecError('hwlink terminal closed before ready', 1, {
|
|
107
|
+
phase: 'opening',
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
99
110
|
if (closed) return;
|
|
100
111
|
closed = true;
|
|
101
112
|
if (onClose) onClose();
|
|
@@ -177,10 +188,12 @@ class HwlinkTerminalExecSession {
|
|
|
177
188
|
});
|
|
178
189
|
|
|
179
190
|
this.readyTimeout = setTimeout(() => {
|
|
180
|
-
this.fail(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
191
|
+
this.fail(
|
|
192
|
+
new WebSocketExecError(`hwlink terminal ready timeout after ${this.timeoutMs}ms`, 124, {
|
|
193
|
+
partialOutput: this.readyBuffer,
|
|
194
|
+
phase: 'opening',
|
|
195
|
+
}),
|
|
196
|
+
);
|
|
184
197
|
}, this.timeoutMs);
|
|
185
198
|
|
|
186
199
|
try {
|
|
@@ -198,10 +211,12 @@ class HwlinkTerminalExecSession {
|
|
|
198
211
|
this.term = new HwlinkTerminalChannel(username);
|
|
199
212
|
this.term.onData((data) => this.handleTerminalData(data));
|
|
200
213
|
this.term.onError((error) => {
|
|
201
|
-
this.fail(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
214
|
+
this.fail(
|
|
215
|
+
new WebSocketExecError(`hwlink terminal error: ${error.message}`, 1, {
|
|
216
|
+
cause: error,
|
|
217
|
+
phase: this.state,
|
|
218
|
+
}),
|
|
219
|
+
);
|
|
205
220
|
});
|
|
206
221
|
this.term.onReady(() => {
|
|
207
222
|
if (Number.isFinite(this.initialCols) && Number.isFinite(this.initialRows)) {
|
|
@@ -211,24 +226,30 @@ class HwlinkTerminalExecSession {
|
|
|
211
226
|
});
|
|
212
227
|
this.term.onClose(() => {
|
|
213
228
|
if (this.state !== 'closed') {
|
|
214
|
-
this.fail(
|
|
215
|
-
|
|
216
|
-
|
|
229
|
+
this.fail(
|
|
230
|
+
new WebSocketExecError('hwlink terminal closed before completion marker', 1, {
|
|
231
|
+
phase: this.state,
|
|
232
|
+
}),
|
|
233
|
+
);
|
|
217
234
|
}
|
|
218
235
|
});
|
|
219
236
|
this.mux.onClose = () => {
|
|
220
237
|
if (this.state !== 'closed') {
|
|
221
|
-
this.fail(
|
|
222
|
-
|
|
223
|
-
|
|
238
|
+
this.fail(
|
|
239
|
+
new WebSocketExecError('hwlink websocket closed before completion marker', 1, {
|
|
240
|
+
phase: this.state,
|
|
241
|
+
}),
|
|
242
|
+
);
|
|
224
243
|
}
|
|
225
244
|
};
|
|
226
245
|
this.mux.onError = (error) => {
|
|
227
246
|
if (this.state !== 'closed') {
|
|
228
|
-
this.fail(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
247
|
+
this.fail(
|
|
248
|
+
new WebSocketExecError(`hwlink websocket error: ${error.message}`, 1, {
|
|
249
|
+
cause: error,
|
|
250
|
+
phase: this.state,
|
|
251
|
+
}),
|
|
252
|
+
);
|
|
232
253
|
}
|
|
233
254
|
};
|
|
234
255
|
|
|
@@ -259,16 +280,20 @@ class HwlinkTerminalExecSession {
|
|
|
259
280
|
this.pending = null;
|
|
260
281
|
|
|
261
282
|
if (wasOpening) {
|
|
262
|
-
this.rejectReady(
|
|
263
|
-
|
|
264
|
-
|
|
283
|
+
this.rejectReady(
|
|
284
|
+
new WebSocketExecError('hwlink terminal session closed before ready', 1, {
|
|
285
|
+
phase: 'opening',
|
|
286
|
+
}),
|
|
287
|
+
);
|
|
265
288
|
}
|
|
266
289
|
|
|
267
290
|
if (pending) {
|
|
268
291
|
clearTimeout(pending.timeout);
|
|
269
|
-
pending.reject(
|
|
270
|
-
|
|
271
|
-
|
|
292
|
+
pending.reject(
|
|
293
|
+
new WebSocketExecError('hwlink terminal session closed before completion marker', 1, {
|
|
294
|
+
phase: 'running',
|
|
295
|
+
}),
|
|
296
|
+
);
|
|
272
297
|
}
|
|
273
298
|
|
|
274
299
|
this.term.close();
|
|
@@ -328,9 +353,11 @@ class HwlinkTerminalExecSession {
|
|
|
328
353
|
|
|
329
354
|
runExec(command, options = {}) {
|
|
330
355
|
if (this.state !== 'ready') {
|
|
331
|
-
return Promise.reject(
|
|
332
|
-
|
|
333
|
-
|
|
356
|
+
return Promise.reject(
|
|
357
|
+
new WebSocketExecError('hwlink terminal exec session is not ready', 1, {
|
|
358
|
+
phase: this.state,
|
|
359
|
+
}),
|
|
360
|
+
);
|
|
334
361
|
}
|
|
335
362
|
|
|
336
363
|
const shellCommand = normalizeCommand(command);
|
|
@@ -347,10 +374,12 @@ class HwlinkTerminalExecSession {
|
|
|
347
374
|
reject,
|
|
348
375
|
resolve,
|
|
349
376
|
timeout: setTimeout(() => {
|
|
350
|
-
this.fail(
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
377
|
+
this.fail(
|
|
378
|
+
new WebSocketExecError(`hwlink terminal exec timeout after ${timeoutMs}ms`, 124, {
|
|
379
|
+
partialOutput: this.pending ? this.pending.buffer : '',
|
|
380
|
+
phase: 'running',
|
|
381
|
+
}),
|
|
382
|
+
);
|
|
354
383
|
}, timeoutMs),
|
|
355
384
|
};
|
|
356
385
|
|
|
@@ -363,10 +392,15 @@ class HwlinkTerminalExecSession {
|
|
|
363
392
|
const pending = this.pending;
|
|
364
393
|
if (!pending) return;
|
|
365
394
|
|
|
366
|
-
const
|
|
367
|
-
|
|
395
|
+
const doneMarker = pending.markers.doneMarker;
|
|
396
|
+
const markerIndex = pending.buffer.lastIndexOf(doneMarker);
|
|
397
|
+
if (markerIndex === -1) return;
|
|
398
|
+
|
|
399
|
+
const afterMarker = pending.buffer.slice(markerIndex + doneMarker.length);
|
|
400
|
+
const exitMatch = afterMarker.match(/^(\d+)/);
|
|
401
|
+
if (!exitMatch) return;
|
|
368
402
|
|
|
369
|
-
const rawOutput = pending.buffer.slice(0,
|
|
403
|
+
const rawOutput = pending.buffer.slice(0, markerIndex);
|
|
370
404
|
const stdout = cleanCommandOutput(rawOutput, {
|
|
371
405
|
inputEchoed: this.inputEchoed,
|
|
372
406
|
command: pending.command,
|
|
@@ -377,7 +411,7 @@ class HwlinkTerminalExecSession {
|
|
|
377
411
|
this.pending = null;
|
|
378
412
|
pending.resolve({
|
|
379
413
|
stdout,
|
|
380
|
-
exitCode: Number(
|
|
414
|
+
exitCode: Number(exitMatch[1]),
|
|
381
415
|
url: this.url,
|
|
382
416
|
source: this.source,
|
|
383
417
|
username: this.username,
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file contains code derived from hwlink.
|
|
3
|
+
*
|
|
4
|
+
* Source:
|
|
5
|
+
* https://gitcode.com/huawei-developers/hwlink
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the ISC License.
|
|
8
|
+
*/
|
|
9
|
+
|
|
1
10
|
const DEFAULT_HIGH_WATERMARK = 8 * 1024 * 1024;
|
|
2
11
|
const DEFAULT_LOW_WATERMARK = 2 * 1024 * 1024;
|
|
3
12
|
|
|
@@ -125,8 +134,4 @@ class FairQueue {
|
|
|
125
134
|
}
|
|
126
135
|
}
|
|
127
136
|
|
|
128
|
-
export {
|
|
129
|
-
DEFAULT_HIGH_WATERMARK,
|
|
130
|
-
DEFAULT_LOW_WATERMARK,
|
|
131
|
-
FairQueue,
|
|
132
|
-
};
|
|
137
|
+
export { DEFAULT_HIGH_WATERMARK, DEFAULT_LOW_WATERMARK, FairQueue };
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file contains code derived from hwlink.
|
|
3
|
+
*
|
|
4
|
+
* Source:
|
|
5
|
+
* https://gitcode.com/huawei-developers/hwlink
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the ISC License.
|
|
8
|
+
*/
|
|
9
|
+
|
|
1
10
|
import { Buffer } from 'node:buffer';
|
|
2
11
|
|
|
3
12
|
import { FairQueue } from './hwlink-fair-queue.js';
|
|
@@ -81,12 +90,7 @@ function sendBinary(ws, data) {
|
|
|
81
90
|
|
|
82
91
|
class HwlinkWebSocketMultiplexer {
|
|
83
92
|
constructor(url, source, options = {}) {
|
|
84
|
-
const {
|
|
85
|
-
WebSocketImpl = globalThis.WebSocket,
|
|
86
|
-
protocol = 'devenv',
|
|
87
|
-
onFrame,
|
|
88
|
-
trace = false,
|
|
89
|
-
} = options;
|
|
93
|
+
const { WebSocketImpl = globalThis.WebSocket, protocol = 'devenv', onFrame, trace = false } = options;
|
|
90
94
|
|
|
91
95
|
if (typeof WebSocketImpl !== 'function') {
|
|
92
96
|
throw new Error('global WebSocket is unavailable; use Node.js 22+ or pass WebSocketImpl');
|
|
@@ -122,9 +126,7 @@ class HwlinkWebSocketMultiplexer {
|
|
|
122
126
|
});
|
|
123
127
|
|
|
124
128
|
addWsListener(this.ws, 'error', (eventOrError) => {
|
|
125
|
-
const error = eventOrError instanceof Error
|
|
126
|
-
? eventOrError
|
|
127
|
-
: new Error('hwlink websocket error');
|
|
129
|
+
const error = eventOrError instanceof Error ? eventOrError : new Error('hwlink websocket error');
|
|
128
130
|
this.handleError(error);
|
|
129
131
|
});
|
|
130
132
|
|
|
@@ -219,9 +221,4 @@ class HwlinkWebSocketMultiplexer {
|
|
|
219
221
|
}
|
|
220
222
|
}
|
|
221
223
|
|
|
222
|
-
export {
|
|
223
|
-
HwlinkWebSocketMultiplexer,
|
|
224
|
-
addWsListener,
|
|
225
|
-
closeQuietly,
|
|
226
|
-
eventDataToUint8Array,
|
|
227
|
-
};
|
|
224
|
+
export { HwlinkWebSocketMultiplexer, addWsListener, closeQuietly, eventDataToUint8Array };
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file contains code derived from hwlink.
|
|
3
|
+
*
|
|
4
|
+
* Source:
|
|
5
|
+
* https://gitcode.com/huawei-developers/hwlink
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the ISC License.
|
|
8
|
+
*/
|
|
9
|
+
|
|
1
10
|
const FIXED_HEADER_LEN = 20;
|
|
2
11
|
const MAX_SEND_CHUNK_SIZE = 8 * 1024;
|
|
3
12
|
|
|
@@ -42,7 +51,7 @@ function isOpTcpTunnelData(op) {
|
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
function isOpFailed(op) {
|
|
45
|
-
return
|
|
54
|
+
return op >> 24 !== 0;
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
function isOpCreateTcpTunnel(op) {
|
|
@@ -70,12 +79,7 @@ function hex2(n) {
|
|
|
70
79
|
}
|
|
71
80
|
|
|
72
81
|
function operationToString(op) {
|
|
73
|
-
return [
|
|
74
|
-
hex2((op >> 24) & 0xff),
|
|
75
|
-
hex2((op >> 16) & 0xff),
|
|
76
|
-
hex2((op >> 8) & 0xff),
|
|
77
|
-
hex2(op & 0xff),
|
|
78
|
-
].join(' ');
|
|
82
|
+
return [hex2((op >> 24) & 0xff), hex2((op >> 16) & 0xff), hex2((op >> 8) & 0xff), hex2(op & 0xff)].join(' ');
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
function formatPacketOneLine(packet) {
|
|
@@ -107,15 +111,7 @@ function toUint8Array(bytes) {
|
|
|
107
111
|
}
|
|
108
112
|
|
|
109
113
|
function createPacket(opts) {
|
|
110
|
-
const {
|
|
111
|
-
operation,
|
|
112
|
-
reserve = 0,
|
|
113
|
-
srcPort = 0,
|
|
114
|
-
dstPort = 0,
|
|
115
|
-
identifier = 0,
|
|
116
|
-
source = 0,
|
|
117
|
-
payload = null,
|
|
118
|
-
} = opts;
|
|
114
|
+
const { operation, reserve = 0, srcPort = 0, dstPort = 0, identifier = 0, source = 0, payload = null } = opts;
|
|
119
115
|
|
|
120
116
|
const payloadLen = payload ? payload.length : 0;
|
|
121
117
|
const total = FIXED_HEADER_LEN + payloadLen;
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file contains code derived from hwlink.
|
|
3
|
+
*
|
|
4
|
+
* Source:
|
|
5
|
+
* https://gitcode.com/huawei-developers/hwlink
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the ISC License.
|
|
8
|
+
*/
|
|
9
|
+
|
|
1
10
|
import {
|
|
2
11
|
OpCode,
|
|
3
12
|
ReserveSource,
|
|
@@ -54,13 +63,15 @@ class HwlinkTerminalChannel {
|
|
|
54
63
|
onopen() {
|
|
55
64
|
if (this.closed || this.opened) return;
|
|
56
65
|
this.opened = true;
|
|
57
|
-
this.sendRaw(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
this.sendRaw(
|
|
67
|
+
createPacket({
|
|
68
|
+
operation: OpCode.OpNewCmdTerminal,
|
|
69
|
+
reserve: ReserveSource.Web,
|
|
70
|
+
identifier: this.identifier,
|
|
71
|
+
source: this.mux ? this.mux.source : 0,
|
|
72
|
+
payload: encoder.encode(this.username),
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
64
75
|
if (this.onReadyCb) this.onReadyCb();
|
|
65
76
|
}
|
|
66
77
|
|
|
@@ -73,13 +84,15 @@ class HwlinkTerminalChannel {
|
|
|
73
84
|
}
|
|
74
85
|
|
|
75
86
|
if (isSubStreamPing(packet.operation)) {
|
|
76
|
-
this.sendRaw(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
87
|
+
this.sendRaw(
|
|
88
|
+
createPacket({
|
|
89
|
+
operation: OpCode.OpCmdTerminalData | OpCode.OpSubStreamPong,
|
|
90
|
+
reserve: ReserveSource.Web,
|
|
91
|
+
identifier: this.identifier,
|
|
92
|
+
source: this.mux ? this.mux.source : 0,
|
|
93
|
+
payload: new Uint8Array(0),
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
83
96
|
return;
|
|
84
97
|
}
|
|
85
98
|
|
|
@@ -105,13 +118,15 @@ class HwlinkTerminalChannel {
|
|
|
105
118
|
}
|
|
106
119
|
|
|
107
120
|
sendTerminalData(payload) {
|
|
108
|
-
this.sendRaw(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
121
|
+
this.sendRaw(
|
|
122
|
+
createPacket({
|
|
123
|
+
operation: OpCode.OpCmdTerminalData | OpCode.OpSubStreamPong,
|
|
124
|
+
reserve: ReserveSource.Web,
|
|
125
|
+
identifier: this.identifier,
|
|
126
|
+
source: this.mux ? this.mux.source : 0,
|
|
127
|
+
payload,
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
115
130
|
}
|
|
116
131
|
|
|
117
132
|
sendText(text) {
|
|
@@ -124,13 +139,15 @@ class HwlinkTerminalChannel {
|
|
|
124
139
|
const view = new DataView(payload.buffer);
|
|
125
140
|
view.setUint16(0, rows, false);
|
|
126
141
|
view.setUint16(2, cols, false);
|
|
127
|
-
this.sendRaw(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
142
|
+
this.sendRaw(
|
|
143
|
+
createPacket({
|
|
144
|
+
operation: OpCode.OpCmdTerminalResize,
|
|
145
|
+
reserve: ReserveSource.Web,
|
|
146
|
+
identifier: this.identifier,
|
|
147
|
+
source: this.mux ? this.mux.source : 0,
|
|
148
|
+
payload,
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
134
151
|
}
|
|
135
152
|
|
|
136
153
|
sendRaw(data) {
|
|
@@ -153,6 +170,4 @@ class HwlinkTerminalChannel {
|
|
|
153
170
|
}
|
|
154
171
|
}
|
|
155
172
|
|
|
156
|
-
export {
|
|
157
|
-
HwlinkTerminalChannel,
|
|
158
|
-
};
|
|
173
|
+
export { HwlinkTerminalChannel };
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file contains code derived from hwlink.
|
|
3
|
+
*
|
|
4
|
+
* Source:
|
|
5
|
+
* https://gitcode.com/huawei-developers/hwlink
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the ISC License.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createServer } from 'node:net';
|
|
11
|
+
import {
|
|
12
|
+
OpCode,
|
|
13
|
+
ReserveSource,
|
|
14
|
+
FIXED_HEADER_LEN,
|
|
15
|
+
MAX_SEND_CHUNK_SIZE,
|
|
16
|
+
createPacket,
|
|
17
|
+
isOpTunnelSuccess,
|
|
18
|
+
isOpTcpTunnelData,
|
|
19
|
+
isOpFailed,
|
|
20
|
+
isSubStreamPing,
|
|
21
|
+
nextIdentifier,
|
|
22
|
+
} from './hwlink-packet.js';
|
|
23
|
+
|
|
24
|
+
const MAX_TUNNEL_PAYLOAD_SIZE = MAX_SEND_CHUNK_SIZE - FIXED_HEADER_LEN;
|
|
25
|
+
|
|
26
|
+
class HwlinkTunnelChannel {
|
|
27
|
+
constructor({ localPort = 0, remotePort, onReady, onClose, onError }) {
|
|
28
|
+
this.localPort = localPort;
|
|
29
|
+
this.remotePort = remotePort;
|
|
30
|
+
this.identifier = nextIdentifier();
|
|
31
|
+
this.mux = null;
|
|
32
|
+
this.closed = false;
|
|
33
|
+
this.opened = false;
|
|
34
|
+
|
|
35
|
+
this.subConnections = new Map();
|
|
36
|
+
this.localServer = null;
|
|
37
|
+
this.nextSubId = 1;
|
|
38
|
+
|
|
39
|
+
this.onReadyCb = onReady || null;
|
|
40
|
+
this.onCloseCb = onClose || null;
|
|
41
|
+
this.onErrorCb = onError || null;
|
|
42
|
+
|
|
43
|
+
this._readyResolve = null;
|
|
44
|
+
this._readyReject = null;
|
|
45
|
+
this.ready = new Promise((resolve, reject) => {
|
|
46
|
+
this._readyResolve = resolve;
|
|
47
|
+
this._readyReject = reject;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
attach(mux) {
|
|
52
|
+
this.mux = mux;
|
|
53
|
+
mux.register(this);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
onopen() {
|
|
57
|
+
if (this.closed || this.opened) return;
|
|
58
|
+
this.opened = true;
|
|
59
|
+
this.startLocalServer();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
onmessage(packet) {
|
|
63
|
+
if (this.closed) return;
|
|
64
|
+
|
|
65
|
+
if (isOpFailed(packet.operation)) {
|
|
66
|
+
const subConn = this.subConnections.get(packet.identifier);
|
|
67
|
+
if (subConn && subConn.socket && !subConn.socket.destroyed) {
|
|
68
|
+
try {
|
|
69
|
+
subConn.socket.end();
|
|
70
|
+
} catch {}
|
|
71
|
+
}
|
|
72
|
+
this.subConnections.delete(packet.identifier);
|
|
73
|
+
this._unregisterSubId(packet.identifier);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (isOpTunnelSuccess(packet.operation)) {
|
|
78
|
+
const subConn = this.subConnections.get(packet.identifier);
|
|
79
|
+
if (subConn && !subConn.ready) {
|
|
80
|
+
subConn.ready = true;
|
|
81
|
+
if (subConn.readyResolve) subConn.readyResolve();
|
|
82
|
+
}
|
|
83
|
+
if (packet.identifier === this.identifier && this._readyResolve) {
|
|
84
|
+
this._readyResolve();
|
|
85
|
+
this._readyResolve = null;
|
|
86
|
+
this._readyReject = null;
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (isOpTcpTunnelData(packet.operation)) {
|
|
92
|
+
const subConn = this.subConnections.get(packet.identifier);
|
|
93
|
+
if (subConn && subConn.socket && !subConn.socket.destroyed) {
|
|
94
|
+
subConn.socket.write(packet.data);
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (isSubStreamPing(packet.operation)) {
|
|
100
|
+
this.sendRaw(
|
|
101
|
+
createPacket({
|
|
102
|
+
operation: OpCode.OpTcpTunnelData | OpCode.OpSubStreamPong,
|
|
103
|
+
reserve: ReserveSource.Web,
|
|
104
|
+
identifier: packet.identifier,
|
|
105
|
+
source: this.mux ? this.mux.source : 0,
|
|
106
|
+
payload: new Uint8Array(0),
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (packet.operation === OpCode.OpDisconnect) {
|
|
113
|
+
const subConn = this.subConnections.get(packet.identifier);
|
|
114
|
+
if (subConn && subConn.socket && !subConn.socket.destroyed) {
|
|
115
|
+
subConn.socket.end();
|
|
116
|
+
}
|
|
117
|
+
this.subConnections.delete(packet.identifier);
|
|
118
|
+
this._unregisterSubId(packet.identifier);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
onerror(error) {
|
|
124
|
+
if (this.onErrorCb) this.onErrorCb(error);
|
|
125
|
+
if (this._readyReject) {
|
|
126
|
+
this._readyReject(error);
|
|
127
|
+
this._readyResolve = null;
|
|
128
|
+
this._readyReject = null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
onclose() {
|
|
133
|
+
this.close();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
startLocalServer() {
|
|
137
|
+
this.localServer = createServer((socket) => {
|
|
138
|
+
this.onIncomingConnection(socket);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
this.localServer.listen(this.localPort, '127.0.0.1', () => {
|
|
142
|
+
this.localPort = this.localServer.address().port;
|
|
143
|
+
if (this.onReadyCb) this.onReadyCb();
|
|
144
|
+
if (this._readyResolve) {
|
|
145
|
+
this._readyResolve();
|
|
146
|
+
this._readyResolve = null;
|
|
147
|
+
this._readyReject = null;
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
this.localServer.on('error', (err) => {
|
|
152
|
+
if (this.onErrorCb) this.onErrorCb(err);
|
|
153
|
+
if (this._readyReject) {
|
|
154
|
+
this._readyReject(err);
|
|
155
|
+
this._readyResolve = null;
|
|
156
|
+
this._readyReject = null;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
_registerSubId(subId) {
|
|
162
|
+
if (this.mux && !this.mux.channels.has(subId)) {
|
|
163
|
+
this.mux.channels.set(subId, this);
|
|
164
|
+
this.mux.queue.register({ identifier: subId });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
_unregisterSubId(subId) {
|
|
169
|
+
if (this.mux) {
|
|
170
|
+
this.mux.channels.delete(subId);
|
|
171
|
+
this.mux.queue.unregister({ identifier: subId });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
onIncomingConnection(socket) {
|
|
176
|
+
const subId = this.nextSubId;
|
|
177
|
+
this.nextSubId = ((this.nextSubId + 1) & 0xffffffff) >>> 0;
|
|
178
|
+
|
|
179
|
+
let readyResolve;
|
|
180
|
+
const readyPromise = new Promise((resolve) => {
|
|
181
|
+
readyResolve = resolve;
|
|
182
|
+
});
|
|
183
|
+
const subConn = {
|
|
184
|
+
identifier: subId,
|
|
185
|
+
socket,
|
|
186
|
+
ready: false,
|
|
187
|
+
readyResolve,
|
|
188
|
+
readyPromise,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
this.subConnections.set(subId, subConn);
|
|
192
|
+
this._registerSubId(subId);
|
|
193
|
+
|
|
194
|
+
this.sendRaw(
|
|
195
|
+
createPacket({
|
|
196
|
+
operation: OpCode.OpCreateTcpTunnel,
|
|
197
|
+
reserve: ReserveSource.Web,
|
|
198
|
+
srcPort: this.localPort,
|
|
199
|
+
dstPort: this.remotePort,
|
|
200
|
+
identifier: subId,
|
|
201
|
+
source: this.mux ? this.mux.source : 0,
|
|
202
|
+
payload: new Uint8Array(0),
|
|
203
|
+
}),
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
socket.on('data', (data) => {
|
|
207
|
+
if (this.closed || socket.destroyed) return;
|
|
208
|
+
for (let offset = 0; offset < data.length; offset += MAX_TUNNEL_PAYLOAD_SIZE) {
|
|
209
|
+
const payload = data.subarray(offset, offset + MAX_TUNNEL_PAYLOAD_SIZE);
|
|
210
|
+
this.sendRaw(
|
|
211
|
+
createPacket({
|
|
212
|
+
operation: OpCode.OpTcpTunnelData,
|
|
213
|
+
reserve: ReserveSource.Web,
|
|
214
|
+
srcPort: this.localPort,
|
|
215
|
+
dstPort: this.remotePort,
|
|
216
|
+
identifier: subId,
|
|
217
|
+
source: this.mux ? this.mux.source : 0,
|
|
218
|
+
payload,
|
|
219
|
+
}),
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
socket.on('close', () => {
|
|
225
|
+
this._unregisterSubId(subId);
|
|
226
|
+
this.subConnections.delete(subId);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
socket.on('error', () => {
|
|
230
|
+
this._unregisterSubId(subId);
|
|
231
|
+
this.subConnections.delete(subId);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
destroySubConnection(subConn, error) {
|
|
236
|
+
if (subConn.socket && !subConn.socket.destroyed) {
|
|
237
|
+
if (error) subConn.socket.destroy(error);
|
|
238
|
+
else subConn.socket.destroy();
|
|
239
|
+
}
|
|
240
|
+
this._unregisterSubId(subConn.identifier);
|
|
241
|
+
this.subConnections.delete(subConn.identifier);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
sendRaw(data) {
|
|
245
|
+
if (this.mux) this.mux.sendFairly(this, data);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
close() {
|
|
249
|
+
if (this.closed) return;
|
|
250
|
+
this.closed = true;
|
|
251
|
+
|
|
252
|
+
for (const subConn of this.subConnections.values()) {
|
|
253
|
+
this.destroySubConnection(subConn);
|
|
254
|
+
}
|
|
255
|
+
this.subConnections.clear();
|
|
256
|
+
|
|
257
|
+
if (this.localServer) {
|
|
258
|
+
this.localServer.close();
|
|
259
|
+
this.localServer = null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (this.mux) this.mux.unregister(this);
|
|
263
|
+
if (this.onCloseCb) this.onCloseCb();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export { HwlinkTunnelChannel };
|