@wenbin_wb/dsh-bridge 2.10.13 → 2.10.14

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,4 +1,4 @@
1
- import { spawn, execSync } from 'node:child_process';
1
+ import { spawn, execFileSync } from 'node:child_process';
2
2
  import { createWriteStream, createReadStream, existsSync, mkdirSync, readFileSync, statSync, renameSync, writeFileSync } from 'node:fs';
3
3
  import { chmod, stat, unlink, rename } from 'node:fs/promises';
4
4
  import { homedir, platform, arch } from 'node:os';
@@ -8,6 +8,19 @@ import { createHash } from 'node:crypto';
8
8
  import { get as httpsGet } from 'node:https';
9
9
  import { get as httpGet } from 'node:http';
10
10
 
11
+ // 参数化执行外部命令,替代原先的 execSync(`"${path}" --version`) 字符串拼接
12
+ // (CodeQL js/shell-command-constructed-from-input)。execFileSync 不经过 shell,
13
+ // 参数以数组传递,路径里的引号 / $( ) / 反引号不再具备注入语义。
14
+ //
15
+ // Windows 例外:.cmd / .bat 无法被 execFileSync 直接执行(Node 要求经 shell),
16
+ // 官方 cloudflared 在 Windows 是 .exe 走参数化路径,只有测试注入的假二进制是
17
+ // .cmd,故按扩展名决定是否需要 shell。macOS / Linux 恒为 false。
18
+ const IS_WINDOWS = process.platform === 'win32';
19
+ function execBinSync(bin, args, options) {
20
+ const needsShell = IS_WINDOWS && /\.(cmd|bat)$/i.test(bin);
21
+ return execFileSync(bin, args, { ...options, shell: needsShell });
22
+ }
23
+
11
24
  const CLOUDFLARED_VERSION = '2024.10.0';
12
25
  const DOWNLOAD_TIMEOUT = 5 * 60 * 1000; // 5 分钟
13
26
  const MIN_BINARY_SIZE = 5 * 1024 * 1024; // 最小 5MB,防止下到 HTML 错误页
@@ -158,7 +171,7 @@ function findSystemCloudflared() {
158
171
  if (bin.includes('/') || bin.includes('\\')) {
159
172
  if (!existsSync(bin)) continue;
160
173
  }
161
- execSync(`"${bin}" --version`, { stdio: 'ignore', timeout: 3000 });
174
+ execBinSync(bin, ['--version'], { stdio: 'ignore', timeout: 3000 });
162
175
  return bin;
163
176
  } catch {}
164
177
  }
@@ -356,7 +369,7 @@ export class CloudflaredManager {
356
369
  // 校验自管理二进制是否匹配期望版本;系统级二进制不校验(尊重用户安装)
357
370
  _checkManagedBinaryVersion(binPath) {
358
371
  try {
359
- const out = execSync(`"${binPath}" --version`, { encoding: 'utf8', timeout: 3000 });
372
+ const out = execBinSync(binPath, ['--version'], { encoding: 'utf8', timeout: 3000 });
360
373
  const ver = parseCloudflaredVersion(out);
361
374
  if (!ver) return { ok: false, reason: `无法解析版本输出: ${(out || '').trim().slice(0, 80)}` };
362
375
  if (ver !== this.binaryVersion) {
@@ -404,7 +417,7 @@ export class CloudflaredManager {
404
417
  if (platform() !== 'win32') {
405
418
  await chmod(binPath, 0o755).catch(() => {});
406
419
  if (platform() === 'darwin') {
407
- try { execSync(`xattr -d com.apple.quarantine "${binPath}"`, { stdio: 'ignore' }); } catch {}
420
+ try { execBinSync('xattr', ['-d', 'com.apple.quarantine', binPath], { stdio: 'ignore' }); } catch {}
408
421
  }
409
422
  }
410
423
  // 版本钉死校验:cloudflared autoupdate 可能已把钉死的版本自替换成新版,
@@ -440,7 +453,7 @@ export class CloudflaredManager {
440
453
 
441
454
  if (url.endsWith('.tgz') || url.endsWith('.tar.gz')) {
442
455
  try {
443
- execSync(`tar -xzf "${tempPath}" -C "${binDir}"`);
456
+ execBinSync('tar', ['-xzf', tempPath, '-C', binDir]);
444
457
  await unlink(tempPath).catch(() => {});
445
458
  } catch (tarErr) {
446
459
  this.logger?.error('解压 cloudflared 压缩包失败: %s', tarErr.message);
@@ -454,7 +467,7 @@ export class CloudflaredManager {
454
467
  if (platform() !== 'win32') {
455
468
  await chmod(binPath, 0o755).catch(() => {});
456
469
  if (platform() === 'darwin') {
457
- try { execSync(`xattr -d com.apple.quarantine "${binPath}"`, { stdio: 'ignore' }); } catch {}
470
+ try { execBinSync('xattr', ['-d', 'com.apple.quarantine', binPath], { stdio: 'ignore' }); } catch {}
458
471
  }
459
472
  }
460
473
 
@@ -14,7 +14,11 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
14
14
  throw Error('Dynamic require of "' + x + '" is not supported');
15
15
  });
16
16
  var __commonJS = (cb, mod) => function __require2() {
17
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
17
+ try {
18
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
19
+ } catch (e) {
20
+ throw mod = 0, e;
21
+ }
18
22
  };
19
23
 
20
24
  // node_modules/delayed-stream/lib/delayed_stream.js
@@ -9296,7 +9300,7 @@ var require_shams = __commonJS({
9296
9300
  return true;
9297
9301
  }
9298
9302
  var obj = {};
9299
- var sym = Symbol("test");
9303
+ var sym = /* @__PURE__ */ Symbol("test");
9300
9304
  var symObj = Object(sym);
9301
9305
  if (typeof sym === "string") {
9302
9306
  return false;
@@ -9355,7 +9359,7 @@ var require_has_symbols = __commonJS({
9355
9359
  if (typeof origSymbol("foo") !== "symbol") {
9356
9360
  return false;
9357
9361
  }
9358
- if (typeof Symbol("bar") !== "symbol") {
9362
+ if (typeof /* @__PURE__ */ Symbol("bar") !== "symbol") {
9359
9363
  return false;
9360
9364
  }
9361
9365
  return hasSymbolSham();
@@ -12499,7 +12503,7 @@ var require_axios = __commonJS({
12499
12503
  });
12500
12504
  return byteStringHeaders;
12501
12505
  }
12502
- var $internals = Symbol("internals");
12506
+ var $internals = /* @__PURE__ */ Symbol("internals");
12503
12507
  function normalizeHeader(header) {
12504
12508
  return header && String(header).trim().toLowerCase();
12505
12509
  }
@@ -13709,7 +13713,7 @@ var require_axios = __commonJS({
13709
13713
  }
13710
13714
  });
13711
13715
  }
13712
- var kInternals = Symbol("internals");
13716
+ var kInternals = /* @__PURE__ */ Symbol("internals");
13713
13717
  var AxiosTransformStream = class extends stream.Transform {
13714
13718
  constructor(options) {
13715
13719
  options = utils$1.toFlatObject(options, {
@@ -14440,9 +14444,9 @@ var require_axios = __commonJS({
14440
14444
  https: httpsFollow
14441
14445
  } = followRedirects;
14442
14446
  var isHttps = /https:?/;
14443
- var kAxiosSocketListener = Symbol("axios.http.socketListener");
14444
- var kAxiosCurrentReq = Symbol("axios.http.currentReq");
14445
- var kAxiosInstalledTunnel = Symbol("axios.http.installedTunnel");
14447
+ var kAxiosSocketListener = /* @__PURE__ */ Symbol("axios.http.socketListener");
14448
+ var kAxiosCurrentReq = /* @__PURE__ */ Symbol("axios.http.currentReq");
14449
+ var kAxiosInstalledTunnel = /* @__PURE__ */ Symbol("axios.http.installedTunnel");
14446
14450
  var tunnelingAgentCache = /* @__PURE__ */ new Map();
14447
14451
  var tunnelingAgentCacheUser = /* @__PURE__ */ new WeakMap();
14448
14452
  var NODE_NATIVE_ENV_PROXY_SUPPORT = {
@@ -20103,10 +20107,10 @@ var require_constants = __commonJS({
20103
20107
  EMPTY_BUFFER: Buffer.alloc(0),
20104
20108
  GUID: "258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
20105
20109
  hasBlob,
20106
- kForOnEventAttribute: Symbol("kIsForOnEventAttribute"),
20107
- kListener: Symbol("kListener"),
20108
- kStatusCode: Symbol("status-code"),
20109
- kWebSocket: Symbol("websocket"),
20110
+ kForOnEventAttribute: /* @__PURE__ */ Symbol("kIsForOnEventAttribute"),
20111
+ kListener: /* @__PURE__ */ Symbol("kListener"),
20112
+ kStatusCode: /* @__PURE__ */ Symbol("status-code"),
20113
+ kWebSocket: /* @__PURE__ */ Symbol("websocket"),
20110
20114
  NOOP: () => {
20111
20115
  }
20112
20116
  };
@@ -20192,8 +20196,8 @@ var require_buffer_util = __commonJS({
20192
20196
  var require_limiter = __commonJS({
20193
20197
  "node_modules/ws/lib/limiter.js"(exports, module) {
20194
20198
  "use strict";
20195
- var kDone = Symbol("kDone");
20196
- var kRun = Symbol("kRun");
20199
+ var kDone = /* @__PURE__ */ Symbol("kDone");
20200
+ var kRun = /* @__PURE__ */ Symbol("kRun");
20197
20201
  var Limiter = class {
20198
20202
  /**
20199
20203
  * Creates a new `Limiter`.
@@ -20248,11 +20252,11 @@ var require_permessage_deflate = __commonJS({
20248
20252
  var { kStatusCode } = require_constants();
20249
20253
  var FastBuffer = Buffer[Symbol.species];
20250
20254
  var TRAILER = Buffer.from([0, 0, 255, 255]);
20251
- var kPerMessageDeflate = Symbol("permessage-deflate");
20252
- var kTotalLength = Symbol("total-length");
20253
- var kCallback = Symbol("callback");
20254
- var kBuffers = Symbol("buffers");
20255
- var kError = Symbol("error");
20255
+ var kPerMessageDeflate = /* @__PURE__ */ Symbol("permessage-deflate");
20256
+ var kTotalLength = /* @__PURE__ */ Symbol("total-length");
20257
+ var kCallback = /* @__PURE__ */ Symbol("callback");
20258
+ var kBuffers = /* @__PURE__ */ Symbol("buffers");
20259
+ var kError = /* @__PURE__ */ Symbol("error");
20256
20260
  var zlibLimiter;
20257
20261
  var PerMessageDeflate = class {
20258
20262
  /**
@@ -21458,7 +21462,7 @@ var require_sender = __commonJS({
21458
21462
  var { EMPTY_BUFFER, kWebSocket, NOOP } = require_constants();
21459
21463
  var { isBlob, isValidStatusCode } = require_validation();
21460
21464
  var { mask: applyMask, toBuffer } = require_buffer_util();
21461
- var kByteLength = Symbol("kByteLength");
21465
+ var kByteLength = /* @__PURE__ */ Symbol("kByteLength");
21462
21466
  var maskBuffer = Buffer.alloc(4);
21463
21467
  var RANDOM_POOL_SIZE = 8 * 1024;
21464
21468
  var randomPool;
@@ -21943,14 +21947,14 @@ var require_event_target = __commonJS({
21943
21947
  "node_modules/ws/lib/event-target.js"(exports, module) {
21944
21948
  "use strict";
21945
21949
  var { kForOnEventAttribute, kListener } = require_constants();
21946
- var kCode = Symbol("kCode");
21947
- var kData = Symbol("kData");
21948
- var kError = Symbol("kError");
21949
- var kMessage = Symbol("kMessage");
21950
- var kReason = Symbol("kReason");
21951
- var kTarget = Symbol("kTarget");
21952
- var kType = Symbol("kType");
21953
- var kWasClean = Symbol("kWasClean");
21950
+ var kCode = /* @__PURE__ */ Symbol("kCode");
21951
+ var kData = /* @__PURE__ */ Symbol("kData");
21952
+ var kError = /* @__PURE__ */ Symbol("kError");
21953
+ var kMessage = /* @__PURE__ */ Symbol("kMessage");
21954
+ var kReason = /* @__PURE__ */ Symbol("kReason");
21955
+ var kTarget = /* @__PURE__ */ Symbol("kTarget");
21956
+ var kType = /* @__PURE__ */ Symbol("kType");
21957
+ var kWasClean = /* @__PURE__ */ Symbol("kWasClean");
21954
21958
  var Event = class {
21955
21959
  /**
21956
21960
  * Create a new `Event`.
@@ -22352,7 +22356,7 @@ var require_websocket = __commonJS({
22352
22356
  } = require_event_target();
22353
22357
  var { format, parse } = require_extension();
22354
22358
  var { toBuffer } = require_buffer_util();
22355
- var kAborted = Symbol("kAborted");
22359
+ var kAborted = /* @__PURE__ */ Symbol("kAborted");
22356
22360
  var protocolVersions = [8, 13];
22357
22361
  var readyStates = ["CONNECTING", "OPEN", "CLOSING", "CLOSED"];
22358
22362
  var subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
@@ -26384,14 +26388,14 @@ var require_index = __commonJS({
26384
26388
  LoggerLevel[LoggerLevel["debug"] = 4] = "debug";
26385
26389
  LoggerLevel[LoggerLevel["trace"] = 5] = "trace";
26386
26390
  })(exports.LoggerLevel || (exports.LoggerLevel = {}));
26387
- var CEventType = Symbol("event-type");
26388
- var CTenantKey = Symbol("tenant-key");
26389
- var CAppTicket = Symbol("app-ticket");
26390
- var CTenantAccessToken = Symbol("tenant-access-token");
26391
- var CWithHelpdeskAuthorization = Symbol("with-helpdesk-authorization");
26392
- var CWithUserAccessToken = Symbol("with-user-access-token");
26393
- var CUserAccessToken = Symbol("user-access-token");
26394
- var CAilySessionRecord = Symbol("aily-session-record");
26391
+ var CEventType = /* @__PURE__ */ Symbol("event-type");
26392
+ var CTenantKey = /* @__PURE__ */ Symbol("tenant-key");
26393
+ var CAppTicket = /* @__PURE__ */ Symbol("app-ticket");
26394
+ var CTenantAccessToken = /* @__PURE__ */ Symbol("tenant-access-token");
26395
+ var CWithHelpdeskAuthorization = /* @__PURE__ */ Symbol("with-helpdesk-authorization");
26396
+ var CWithUserAccessToken = /* @__PURE__ */ Symbol("with-user-access-token");
26397
+ var CUserAccessToken = /* @__PURE__ */ Symbol("user-access-token");
26398
+ var CAilySessionRecord = /* @__PURE__ */ Symbol("aily-session-record");
26395
26399
  var string2Base64 = (content) => Buffer.from(content).toString("base64");
26396
26400
  var DefaultCache = class {
26397
26401
  constructor() {
@@ -26453,12 +26457,19 @@ var require_index = __commonJS({
26453
26457
  return domain;
26454
26458
  }
26455
26459
  };
26456
- var fillApiPath = (apiPath, pathSupplement = {}) => apiPath.replace(/:([^/]+)/g, (_, $1) => {
26457
- if (pathSupplement[$1] !== void 0) {
26458
- return pathSupplement[$1];
26459
- }
26460
- throw new Error(`request miss ${$1} path argument`);
26461
- });
26460
+ var fillApiPath = (apiPath, pathSupplement = {}) => (
26461
+ // Only treat `:name` as a path parameter when it starts a path segment
26462
+ // (string start or right after `/`). Colons inside the URL authority,
26463
+ // e.g. `http://localhost:3000` or `user:pw@host`, must be left untouched.
26464
+ // The separator is captured and re-emitted instead of using a lookbehind
26465
+ // because the compile target (es6) has no lookbehind support.
26466
+ apiPath.replace(/(^|\/):([^/]+)/g, (_, pathPrefix, pathKey) => {
26467
+ if (pathSupplement[pathKey] !== void 0) {
26468
+ return `${pathPrefix}${pathSupplement[pathKey]}`;
26469
+ }
26470
+ throw new Error(`request miss ${pathKey} path argument`);
26471
+ })
26472
+ );
26462
26473
  var assert = (predication, callback) => __awaiter(void 0, void 0, void 0, function* () {
26463
26474
  const isInvoke = typeof predication === "function" ? predication() : predication;
26464
26475
  if (isInvoke) {
@@ -119921,6 +119932,8 @@ var require_index = __commonJS({
119921
119932
  }
119922
119933
  };
119923
119934
  var CAppTicketHandle = "app_ticket";
119935
+ var duplicateHandleWarning = (key) => `${key} handle is already registered and has been replaced, call unregister('${key}') first to replace it explicitly`;
119936
+ var appTicketUnregisteredWarning = `the built-in ${CAppTicketHandle} handle is unregistered, pushed app tickets are no longer cached and every app_access_token request of an ISV app will miss the cache and trigger an app_ticket resend`;
119924
119937
  var EventDispatcher = class {
119925
119938
  constructor(params) {
119926
119939
  this.verificationToken = "";
@@ -119954,16 +119967,46 @@ var require_index = __commonJS({
119954
119967
  })
119955
119968
  });
119956
119969
  }
119970
+ /**
119971
+ * Register a handler for each of the given event keys.
119972
+ *
119973
+ * Registering a key that already has a handler replaces it. That is a
119974
+ * supported operation, but the replacement is easier to spot when it is
119975
+ * announced: call `unregister(key)` first and nothing is logged, otherwise
119976
+ * a warning flags what would otherwise be an unnoticed overwrite.
119977
+ */
119957
119978
  register(handles) {
119958
119979
  Object.keys(handles).forEach((key) => {
119959
119980
  if (this.handles.has(key) && key !== CAppTicketHandle) {
119960
- this.logger.error(`this ${key} handle is registered`);
119981
+ this.logger.warn(duplicateHandleWarning(key));
119961
119982
  }
119962
119983
  this.handles.set(key, handles[key]);
119963
119984
  this.logger.debug(`register ${key} handle`);
119964
119985
  });
119965
119986
  return this;
119966
119987
  }
119988
+ /**
119989
+ * Remove the handler registered for each of the given event keys, so that
119990
+ * the events are no longer dispatched. Keys without a handler are skipped.
119991
+ *
119992
+ * Removing the built-in `app_ticket` handle stops the SDK from caching
119993
+ * pushed app tickets, which makes every app_access_token request of an ISV
119994
+ * app miss the cache and trigger an app_ticket resend -- hence the warning.
119995
+ */
119996
+ unregister(...keys) {
119997
+ keys.forEach((key) => {
119998
+ if (!this.handles.has(key)) {
119999
+ this.logger.debug(`no ${key} handle to unregister`);
120000
+ return;
120001
+ }
120002
+ this.handles.delete(key);
120003
+ this.logger.debug(`unregister ${key} handle`);
120004
+ if (key === CAppTicketHandle) {
120005
+ this.logger.warn(appTicketUnregisteredWarning);
120006
+ }
120007
+ });
120008
+ return this;
120009
+ }
119967
120010
  invoke(data, params) {
119968
120011
  var _a, _b;
119969
120012
  return __awaiter(this, void 0, void 0, function* () {
@@ -120686,6 +120729,9 @@ var require_index = __commonJS({
120686
120729
  }
120687
120730
  mergeData(params) {
120688
120731
  const { message_id, sum, seq, trace_id, data } = params;
120732
+ if (!Number.isInteger(sum) || sum <= 0 || !Number.isInteger(seq) || seq < 0 || seq >= sum) {
120733
+ throw new Error(`invalid event fragment metadata (sum: ${sum}, seq: ${seq})`);
120734
+ }
120689
120735
  const cache = this.cache.get(message_id);
120690
120736
  if (!cache) {
120691
120737
  const buffer = new Array(sum).fill(void 0);
@@ -120697,6 +120743,9 @@ var require_index = __commonJS({
120697
120743
  create_time: Date.now()
120698
120744
  });
120699
120745
  } else {
120746
+ if (sum !== cache.buffer.length) {
120747
+ throw new Error(`invalid event fragment metadata (sum: ${sum} differs from first fragment's ${cache.buffer.length}, seq: ${seq})`);
120748
+ }
120700
120749
  cache.buffer[seq] = data;
120701
120750
  }
120702
120751
  const mergedCache = this.cache.get(message_id);
@@ -120787,12 +120836,17 @@ var require_index = __commonJS({
120787
120836
  HttpStatusCode2[HttpStatusCode2["ok"] = 200] = "ok";
120788
120837
  HttpStatusCode2[HttpStatusCode2["internal_server_error"] = 500] = "internal_server_error";
120789
120838
  })(HttpStatusCode || (HttpStatusCode = {}));
120839
+ var describeError = (err) => {
120840
+ const message = err === null || err === void 0 ? void 0 : err.message;
120841
+ return message ? message : String(err);
120842
+ };
120790
120843
  var WSClient = class {
120791
120844
  constructor(params) {
120792
120845
  var _a;
120793
120846
  this.wsConfig = new WSConfig();
120794
120847
  this.reconnectGeneration = 0;
120795
120848
  this.isConnecting = false;
120849
+ this.closed = false;
120796
120850
  this.reconnectInfo = {
120797
120851
  lastConnectTime: 0,
120798
120852
  nextConnectTime: 0
@@ -120861,6 +120915,53 @@ var require_index = __commonJS({
120861
120915
  this.livenessTimer = void 0;
120862
120916
  }
120863
120917
  }
120918
+ /**
120919
+ * Detach a socket we are about to throw away: drop every listener, but
120920
+ * leave one 'error' sink behind.
120921
+ *
120922
+ * `ws` reports the abort of a non-OPEN socket asynchronously — both
120923
+ * `terminate()` and `close()` route through `abortHandshake()`, which
120924
+ * emits 'error' on a later tick — and a non-force `close()` on an OPEN
120925
+ * socket keeps the connection alive until the peer answers (up to `ws`'s
120926
+ * 30s closeTimeout), during which a TCP reset still surfaces as 'error'.
120927
+ * An EventEmitter with no 'error' listener throws instead of emitting, and
120928
+ * that throw lands outside every try/catch, killing the host process.
120929
+ * See https://github.com/larksuite/node-sdk/issues/197
120930
+ *
120931
+ * NOTE: not for the liveness watchdog — that one terminates *on purpose*
120932
+ * and needs its listeners intact so 'close' drives the reconnect.
120933
+ */
120934
+ detachSocket(socket) {
120935
+ socket.removeAllListeners();
120936
+ socket.on("error", (err) => {
120937
+ this.logger.debug("[ws]", "ignored error from discarded socket", err === null || err === void 0 ? void 0 : err.message);
120938
+ });
120939
+ }
120940
+ /**
120941
+ * Throw away whatever the current attempt established — socket *and* the
120942
+ * timers its 'open' handler armed. A handshake that completes after
120943
+ * `close()` runs `pingLoop()`, and that timer is not unref'd: leaving it
120944
+ * behind keeps the event loop alive forever on a client the caller closed.
120945
+ *
120946
+ * Only safe when {@link closed} is set: otherwise a newer session may
120947
+ * already own `wsConfig`'s instance.
120948
+ */
120949
+ discardConnection() {
120950
+ if (this.pingInterval) {
120951
+ clearTimeout(this.pingInterval);
120952
+ this.pingInterval = void 0;
120953
+ }
120954
+ this.clearLiveness();
120955
+ const socket = this.wsConfig.getWSInstance();
120956
+ if (!socket)
120957
+ return;
120958
+ this.detachSocket(socket);
120959
+ try {
120960
+ socket.terminate();
120961
+ } catch (_a) {
120962
+ }
120963
+ this.wsConfig.setWSInstance(null);
120964
+ }
120864
120965
  /**
120865
120966
  * Invoke a user-supplied callback safely: no-op if undefined, swallow any
120866
120967
  * exception to avoid breaking the WS state machine.
@@ -120967,7 +121068,7 @@ var require_index = __commonJS({
120967
121068
  if (this.handshakeTimeoutMs && this.handshakeTimeoutMs > 0) {
120968
121069
  timer = setTimeout(() => {
120969
121070
  this.logger.error("[ws]", `handshake timeout after ${this.handshakeTimeoutMs}ms`);
120970
- wsInstance.removeAllListeners();
121071
+ this.detachSocket(wsInstance);
120971
121072
  try {
120972
121073
  wsInstance.terminate();
120973
121074
  } catch (_a) {
@@ -121023,6 +121124,13 @@ var require_index = __commonJS({
121023
121124
  } finally {
121024
121125
  this.isConnecting = false;
121025
121126
  }
121127
+ if (this.closed) {
121128
+ this.discardConnection();
121129
+ return;
121130
+ }
121131
+ if (currentGeneration !== this.reconnectGeneration) {
121132
+ return;
121133
+ }
121026
121134
  if (result.ok) {
121027
121135
  this.hasEverConnected = true;
121028
121136
  this.safeInvoke("onReady", this.onReady);
@@ -121064,6 +121172,10 @@ var require_index = __commonJS({
121064
121172
  count++;
121065
121173
  this.currentReconnectAttempts = count;
121066
121174
  const result = yield tryConnect();
121175
+ if (this.closed) {
121176
+ this.discardConnection();
121177
+ return;
121178
+ }
121067
121179
  if (currentGeneration !== this.reconnectGeneration) {
121068
121180
  return;
121069
121181
  }
@@ -121127,13 +121239,17 @@ var require_index = __commonJS({
121127
121239
  const wsInstance = this.wsConfig.getWSInstance();
121128
121240
  wsInstance === null || wsInstance === void 0 ? void 0 : wsInstance.on("message", (buffer) => __awaiter(this, void 0, void 0, function* () {
121129
121241
  this.clearLiveness();
121130
- const data = decode(buffer);
121131
- const { method } = data;
121132
- if (method === FrameType.control) {
121133
- yield this.handleControlData(data);
121134
- }
121135
- if (method === FrameType.data) {
121136
- yield this.handleEventData(data);
121242
+ try {
121243
+ const data = decode(buffer);
121244
+ const { method } = data;
121245
+ if (method === FrameType.control) {
121246
+ yield this.handleControlData(data);
121247
+ }
121248
+ if (method === FrameType.data) {
121249
+ yield this.handleEventData(data);
121250
+ }
121251
+ } catch (err) {
121252
+ this.logger.error("[ws]", `failed to handle inbound frame: ${describeError(err)}`);
121137
121253
  }
121138
121254
  }));
121139
121255
  wsInstance === null || wsInstance === void 0 ? void 0 : wsInstance.on("error", (e) => {
@@ -121155,8 +121271,15 @@ var require_index = __commonJS({
121155
121271
  }
121156
121272
  if (type === MessageType.pong && payload) {
121157
121273
  this.logger.trace("[ws]", "receive pong");
121158
- const dataString = new TextDecoder("utf-8").decode(payload);
121159
- const { PingInterval, ReconnectCount, ReconnectInterval, ReconnectNonce } = JSON.parse(dataString);
121274
+ let pong;
121275
+ try {
121276
+ const dataString = new TextDecoder("utf-8").decode(payload);
121277
+ pong = JSON.parse(dataString);
121278
+ } catch (err) {
121279
+ this.logger.error("[ws]", `invalid pong payload: ${describeError(err)}`);
121280
+ return;
121281
+ }
121282
+ const { PingInterval, ReconnectCount, ReconnectInterval, ReconnectNonce } = pong;
121160
121283
  this.wsConfig.updateWs({
121161
121284
  pingInterval: PingInterval * 1e3,
121162
121285
  reconnectCount: ReconnectCount,
@@ -121179,13 +121302,19 @@ var require_index = __commonJS({
121179
121302
  if (type !== MessageType.event) {
121180
121303
  return;
121181
121304
  }
121182
- const mergedData = this.dataCache.mergeData({
121183
- message_id,
121184
- sum: Number(sum),
121185
- seq: Number(seq),
121186
- trace_id,
121187
- data: payload
121188
- });
121305
+ let mergedData;
121306
+ try {
121307
+ mergedData = this.dataCache.mergeData({
121308
+ message_id,
121309
+ sum: Number(sum),
121310
+ seq: Number(seq),
121311
+ trace_id,
121312
+ data: payload
121313
+ });
121314
+ } catch (err) {
121315
+ this.logger.error("[ws]", `failed to merge event fragments, message_id: ${message_id}; trace_id: ${trace_id}; error: ${describeError(err)}`);
121316
+ return;
121317
+ }
121189
121318
  if (!mergedData) {
121190
121319
  return;
121191
121320
  }
@@ -121254,6 +121383,7 @@ var require_index = __commonJS({
121254
121383
  */
121255
121384
  close(params = {}) {
121256
121385
  const { force = false } = params;
121386
+ this.closed = true;
121257
121387
  this.reconnectGeneration++;
121258
121388
  if (this.pingInterval) {
121259
121389
  clearTimeout(this.pingInterval);
@@ -121269,7 +121399,7 @@ var require_index = __commonJS({
121269
121399
  this.currentReconnectAttempts = 0;
121270
121400
  const wsInstance = this.wsConfig.getWSInstance();
121271
121401
  if (wsInstance) {
121272
- wsInstance.removeAllListeners();
121402
+ this.detachSocket(wsInstance);
121273
121403
  if (force) {
121274
121404
  wsInstance.terminate();
121275
121405
  } else {
@@ -121292,6 +121422,7 @@ var require_index = __commonJS({
121292
121422
  return;
121293
121423
  }
121294
121424
  this.terminalError = false;
121425
+ this.closed = false;
121295
121426
  this.currentReconnectAttempts = 0;
121296
121427
  this.dataCache.clearAtInterval();
121297
121428
  this.logger.info("[ws]", `receive events or callbacks through persistent connection only available in self-build & Feishu app, Configured in:
@@ -121711,6 +121842,7 @@ var require_index = __commonJS({
121711
121842
  let domainSwitched = false;
121712
121843
  let pollTimer = null;
121713
121844
  let expireTimer = null;
121845
+ let settled = false;
121714
121846
  const cleanup = () => {
121715
121847
  var _a2;
121716
121848
  if (pollTimer !== null) {
@@ -121723,25 +121855,51 @@ var require_index = __commonJS({
121723
121855
  }
121724
121856
  (_a2 = ctx.signal) === null || _a2 === void 0 ? void 0 : _a2.removeEventListener("abort", onAbort);
121725
121857
  };
121726
- const onAbort = () => {
121858
+ const succeed = (result) => {
121859
+ if (settled) {
121860
+ return;
121861
+ }
121862
+ settled = true;
121863
+ cleanup();
121864
+ resolve(result);
121865
+ };
121866
+ const fail = (err) => {
121867
+ if (settled) {
121868
+ return;
121869
+ }
121870
+ settled = true;
121727
121871
  cleanup();
121728
- reject(createError("abort", "Registration was aborted"));
121872
+ reject(err);
121873
+ };
121874
+ const scheduleNextPoll = () => {
121875
+ if (settled) {
121876
+ return;
121877
+ }
121878
+ pollTimer = setTimeout(poll, interval);
121879
+ };
121880
+ const onAbort = () => {
121881
+ fail(createError("abort", "Registration was aborted"));
121729
121882
  };
121730
121883
  if ((_a = ctx.signal) === null || _a === void 0 ? void 0 : _a.aborted) {
121731
- return reject(createError("abort", "Registration was aborted"));
121884
+ return fail(createError("abort", "Registration was aborted"));
121732
121885
  }
121733
121886
  (_b = ctx.signal) === null || _b === void 0 ? void 0 : _b.addEventListener("abort", onAbort, { once: true });
121734
121887
  expireTimer = setTimeout(() => {
121735
- cleanup();
121736
- reject(createError("expired_token", "Polling timed out"));
121888
+ fail(createError("expired_token", "Polling timed out"));
121737
121889
  }, ctx.expireIn);
121738
121890
  const poll = () => __awaiter(this, void 0, void 0, function* () {
121739
121891
  var _c, _d, _e, _f, _g, _h;
121892
+ if (settled) {
121893
+ return;
121894
+ }
121740
121895
  try {
121741
121896
  const pollRes = yield requestRegistration(baseUrl, {
121742
121897
  action: "poll",
121743
121898
  device_code: ctx.deviceCode
121744
121899
  });
121900
+ if (settled) {
121901
+ return;
121902
+ }
121745
121903
  if (((_c = pollRes.user_info) === null || _c === void 0 ? void 0 : _c.tenant_brand) === "lark" && !domainSwitched) {
121746
121904
  baseUrl = ctx.larkBaseUrl;
121747
121905
  domainSwitched = true;
@@ -121750,8 +121908,7 @@ var require_index = __commonJS({
121750
121908
  return;
121751
121909
  }
121752
121910
  if (pollRes.client_id && pollRes.client_secret) {
121753
- cleanup();
121754
- resolve({
121911
+ succeed({
121755
121912
  client_id: pollRes.client_id,
121756
121913
  client_secret: pollRes.client_secret,
121757
121914
  user_info: pollRes.user_info
@@ -121768,21 +121925,18 @@ var require_index = __commonJS({
121768
121925
  break;
121769
121926
  case "access_denied":
121770
121927
  case "expired_token":
121771
- cleanup();
121772
- reject(createError(pollRes.error, (_g = pollRes.error_description) !== null && _g !== void 0 ? _g : "Unknown error"));
121928
+ fail(createError(pollRes.error, (_g = pollRes.error_description) !== null && _g !== void 0 ? _g : "Unknown error"));
121773
121929
  return;
121774
121930
  default:
121775
121931
  if (pollRes.error) {
121776
- cleanup();
121777
- reject(createError(pollRes.error, (_h = pollRes.error_description) !== null && _h !== void 0 ? _h : "Unknown error"));
121932
+ fail(createError(pollRes.error, (_h = pollRes.error_description) !== null && _h !== void 0 ? _h : "Unknown error"));
121778
121933
  return;
121779
121934
  }
121780
121935
  break;
121781
121936
  }
121782
- pollTimer = setTimeout(poll, interval);
121937
+ scheduleNextPoll();
121783
121938
  } catch (e) {
121784
- cleanup();
121785
- reject(e);
121939
+ fail(e);
121786
121940
  }
121787
121941
  });
121788
121942
  poll();
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@wenbin_wb/dsh-bridge",
3
- "version": "2.10.13",
3
+ "version": "2.10.14",
4
4
  "description": "手机扫码即可在移动端/公网继续用 DeepSeek Harness,人不在电脑前也能接着干。一键局域网二维码、Cloudflare 公网隧道、自建隧道与微信 / QQ / 飞书 / Telegram Bot(多工作区/会话持久化/媒体/卡片审批/流式输出),无需自己搭公网服务器。",
5
- "releaseNotes": "【v2.10.13】可靠性加固 + 跟随 DSH 0.1.5 目录选择器\n• 新增:cloudflared 运行时健康探针——每 30s 探活官方 /ready,连续 3 次失败(约 90s)面板可见降级,连续 10 次(约 5min)判定假死自动强制重建,重建永不进终态(网络恢复后自愈)\n• 新增:cloudflared 输出落盘 ~/.dsh/dsh-bridge/cloudflared.log(5MB 轮转 .1),Token 跨 chunk 强制脱敏\n• 修复:连接器假死时公网入口静默失效数天无人发现(HTTP 530 / error code 1033),旧逻辑只监听进程退出故永不触发\n• 修复:本机点「添加工作区」误弹插件的远程抽屉(issue #28)——DSH 0.1.5 自带官方目录选择器且 slots 为 shadow 语义,插件同名 Slot 覆盖了官方实现;现本机 + 官方 picker 可用时不再注册该 Slot(macOS/Windows 走系统原生对话框、无桌面 Linux 走官方网页选择器),旧版 DSH 与远程/移动仍由插件兜底\n• 修复:添加工作区弹窗点过「已注册工作区」后永久卡死(PR #42)——switchToWorkspace 只置位 isSubmitting 从不复位,改为与 doSubmit 对称的 try/finally\n• 修复:探针路径任何异常(畸形地址、日志通道抛错)都不会拖垮 DSH 主进程\n• 测试:新增 18 条探针 + 12 条目录选择器让位 + 3 条弹窗状态复位回归测试\n• 说明:本版需重启一次 dsh(systemctl --user restart dsh-web.service)才生效",
5
+ "releaseNotes": "【v2.10.14】移动端设置页体验修复\n• 修复:手机设置页内容被裁掉、部分页签点不到(含第三方设置页),竖屏改为「分类列表 → 内容页」两级导航,字号与触控行恢复到可读可点,横屏分类不再被切\n• 修复:设置页「✕」回到标题行右上角,页面操作按钮统一收到底部动作栏\n• 修复:插件市场点「安装」没反应(确认框被设置弹窗盖住),横屏下确认框还会超出屏幕、按钮点不到;现均可正常显示与点击\n• 说明:纯前端样式与交互调整,刷新页面即生效(无需重启 dsh)",
6
6
  "type": "module",
7
7
  "main": "lib/index.js",
8
8
  "exports": {
@@ -36,10 +36,10 @@
36
36
  "ws": "^8.18.0"
37
37
  },
38
38
  "devDependencies": {
39
- "@deepseek-ai/dsh-llm": "0.1.0-rc.6",
39
+ "@deepseek-ai/dsh-llm": "0.1.0-rc.8",
40
40
  "@eslint/js": "^10.0.1",
41
41
  "@larksuiteoapi/node-sdk": "^1.73.0",
42
- "esbuild": "^0.25.9",
42
+ "esbuild": "^0.28.2",
43
43
  "eslint": "^10.9.1",
44
44
  "globals": "^17.11.0",
45
45
  "puppeteer-core": "^25.8.0"