@yoooclaw/phone-notifications 1.11.12 → 1.11.13

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/dist/index.cjs CHANGED
@@ -5574,7 +5574,7 @@ function readBuildInjectedVersion() {
5574
5574
  if (false) {
5575
5575
  return void 0;
5576
5576
  }
5577
- const version = "1.11.12".trim();
5577
+ const version = "1.11.13".trim();
5578
5578
  return version || void 0;
5579
5579
  }
5580
5580
  function readPluginVersionFromPackageJson() {
@@ -12303,19 +12303,72 @@ var import_websocket = __toESM(require_websocket(), 1);
12303
12303
  var import_websocket_server = __toESM(require_websocket_server(), 1);
12304
12304
  var wrapper_default = import_websocket.default;
12305
12305
 
12306
- // src/tunnel/relay-client.ts
12307
- function previewText(text, max = 500) {
12306
+ // src/tunnel/utils.ts
12307
+ function previewText(text, max = 200) {
12308
+ if (!text) return "";
12308
12309
  return text.length <= max ? text : `${text.substring(0, max)}\u2026`;
12309
12310
  }
12310
- var HANDSHAKE_TIMEOUT_MS = 15e3;
12311
- var CONNECT_WATCHDOG_MS = 2e4;
12312
12311
  function maskSecret(value) {
12313
12312
  if (!value) return "empty";
12314
12313
  if (value.length <= 8) {
12315
- return `${value.slice(0, 2)}\u2026${value.slice(-2)}`;
12314
+ return `${value.slice(0, 2)}...${value.slice(-2)}`;
12316
12315
  }
12317
- return `${value.slice(0, 4)}\u2026${value.slice(-4)}`;
12316
+ return `${value.slice(0, 4)}...${value.slice(-4)}`;
12318
12317
  }
12318
+ function redactUrlSecrets(rawUrl) {
12319
+ try {
12320
+ const url = new URL(rawUrl);
12321
+ for (const key of ["apiKey", "token", "access_token"]) {
12322
+ const value = url.searchParams.get(key);
12323
+ if (value) {
12324
+ url.searchParams.set(key, maskSecret(value));
12325
+ }
12326
+ }
12327
+ return url.toString();
12328
+ } catch {
12329
+ return rawUrl.replace(
12330
+ /([?&](?:apiKey|token|access_token)=)([^&]+)/gi,
12331
+ (_match, prefix, value) => {
12332
+ let decoded = value;
12333
+ try {
12334
+ decoded = decodeURIComponent(value);
12335
+ } catch {
12336
+ }
12337
+ return `${prefix}${maskSecret(decoded)}`;
12338
+ }
12339
+ );
12340
+ }
12341
+ }
12342
+ function findHeaderValue(headers, key) {
12343
+ if (!headers) return void 0;
12344
+ const lowerKey = key.toLowerCase();
12345
+ for (const [headerKey, headerValue] of Object.entries(headers)) {
12346
+ if (headerKey.toLowerCase() === lowerKey) {
12347
+ return headerValue;
12348
+ }
12349
+ }
12350
+ return void 0;
12351
+ }
12352
+ function summarizeRequestHeaders(headers) {
12353
+ const contentType = findHeaderValue(headers, "content-type");
12354
+ const requestId = findHeaderValue(headers, "x-request-id");
12355
+ const parts = [];
12356
+ if (contentType) {
12357
+ parts.push(`contentType=${contentType}`);
12358
+ }
12359
+ if (requestId) {
12360
+ parts.push(`xRequestId=${previewText(requestId, 120)}`);
12361
+ }
12362
+ return parts.length ? `, ${parts.join(", ")}` : "";
12363
+ }
12364
+
12365
+ // src/tunnel/relay-client.ts
12366
+ function previewText2(text, max = 500) {
12367
+ return text.length <= max ? text : `${text.substring(0, max)}\u2026`;
12368
+ }
12369
+ var HANDSHAKE_TIMEOUT_MS = 15e3;
12370
+ var CONNECT_WATCHDOG_MS = 2e4;
12371
+ var SEND_SKIPPED_LOG_INTERVAL_MS = 3e4;
12319
12372
  var RelayClient = class {
12320
12373
  constructor(opts) {
12321
12374
  this.opts = opts;
@@ -12329,6 +12382,8 @@ var RelayClient = class {
12329
12382
  aborted = false;
12330
12383
  lastInboundAt = 0;
12331
12384
  stopPromise = null;
12385
+ skippedSendLogLastAt = null;
12386
+ skippedSendLogSuppressed = 0;
12332
12387
  /** 写连接状态到磁盘 */
12333
12388
  writeStatus(state, lastDisconnectReason) {
12334
12389
  if (!this.opts.statusFilePath) return;
@@ -12432,9 +12487,7 @@ var RelayClient = class {
12432
12487
  );
12433
12488
  this.ws.send(payload);
12434
12489
  } else {
12435
- this.opts.logger.warn(
12436
- `Relay tunnel: \u25B6 send skipped, ws not open (readyState=${this.ws?.readyState ?? "null"}), frame type=${frame.type}`
12437
- );
12490
+ this.logSendSkipped("send", `frame type=${frame.type}`);
12438
12491
  }
12439
12492
  }
12440
12493
  /** 原样透传文本到 Relay(用于 Gateway WS 响应直接回传) */
@@ -12445,9 +12498,7 @@ var RelayClient = class {
12445
12498
  );
12446
12499
  this.ws.send(text);
12447
12500
  } else {
12448
- this.opts.logger.warn(
12449
- `Relay tunnel: \u25B6 sendRaw skipped, ws not open (readyState=${this.ws?.readyState ?? "null"})`
12450
- );
12501
+ this.logSendSkipped("sendRaw");
12451
12502
  }
12452
12503
  }
12453
12504
  /** 启动连接,带自动重连,直到 abortSignal 触发 */
@@ -12473,7 +12524,7 @@ var RelayClient = class {
12473
12524
  this.cleanup(true);
12474
12525
  const rawApiKey = this.opts.apiKey.startsWith("Bearer ") ? this.opts.apiKey.slice("Bearer ".length) : this.opts.apiKey;
12475
12526
  this.opts.logger.info(
12476
- `Relay tunnel: connecting to ${this.opts.tunnelUrl} (attempt=${this.reconnectAttempt}, heartbeat=${this.opts.heartbeatSec}s, apiKey=${maskSecret(rawApiKey)})`
12527
+ `Relay tunnel: connecting to ${redactUrlSecrets(this.opts.tunnelUrl)} (attempt=${this.reconnectAttempt}, heartbeat=${this.opts.heartbeatSec}s, apiKey=${maskSecret(rawApiKey)})`
12477
12528
  );
12478
12529
  this.writeStatus("connecting");
12479
12530
  return new Promise((resolve) => {
@@ -12567,7 +12618,7 @@ var RelayClient = class {
12567
12618
  const reasonStr = reason.toString();
12568
12619
  const lastInboundAgoMs = this.lastInboundAt ? Date.now() - this.lastInboundAt : null;
12569
12620
  const isCurrentSocket = this.ws === ws;
12570
- const logMessage = `Relay tunnel: disconnected (code=${code}, reason=${previewText(reasonStr, 200)}, lastInboundAgoMs=${lastInboundAgoMs ?? "N/A"}, reconnectAttempt=${this.reconnectAttempt})`;
12621
+ const logMessage = `Relay tunnel: disconnected (code=${code}, reason=${previewText2(reasonStr, 200)}, lastInboundAgoMs=${lastInboundAgoMs ?? "N/A"}, reconnectAttempt=${this.reconnectAttempt})`;
12571
12622
  if (this.aborted || !isCurrentSocket) {
12572
12623
  this.opts.logger.info(logMessage);
12573
12624
  } else {
@@ -12582,11 +12633,12 @@ var RelayClient = class {
12582
12633
  settle();
12583
12634
  });
12584
12635
  ws.on("error", (err2) => {
12636
+ clearConnectWatchdog();
12585
12637
  this.opts.logger.error(
12586
- `Relay tunnel: WebSocket error: ${err2.message} (readyState=${ws.readyState}, reconnectAttempt=${this.reconnectAttempt}, url=${wsUrl.toString()})`
12638
+ `Relay tunnel: WebSocket error: ${err2.message} (readyState=${ws.readyState}, reconnectAttempt=${this.reconnectAttempt}, url=${redactUrlSecrets(wsUrl.toString())})`
12587
12639
  );
12588
12640
  if (this.ws === ws) {
12589
- this.scheduleReconnect();
12641
+ this.forceReconnectFromSocket(ws, `error: ${err2.message}`);
12590
12642
  }
12591
12643
  settle();
12592
12644
  });
@@ -12608,14 +12660,14 @@ var RelayClient = class {
12608
12660
  return;
12609
12661
  }
12610
12662
  this.opts.logger.info(
12611
- `Relay tunnel: \u2605 received message (${text.length} chars): ${previewText(text)}`
12663
+ `Relay tunnel: \u2605 received message (${text.length} chars): ${previewText2(text)}`
12612
12664
  );
12613
12665
  let frame;
12614
12666
  try {
12615
12667
  frame = JSON.parse(text);
12616
12668
  } catch {
12617
12669
  this.opts.logger.warn(
12618
- `Relay tunnel: received invalid frame, ignoring (preview=${previewText(text, 200)})`
12670
+ `Relay tunnel: received invalid frame, ignoring (preview=${previewText2(text, 200)})`
12619
12671
  );
12620
12672
  return;
12621
12673
  }
@@ -12651,7 +12703,8 @@ var RelayClient = class {
12651
12703
  return this.opts.heartbeatSec * 3 * 1e3;
12652
12704
  }
12653
12705
  sendHeartbeat() {
12654
- if (this.ws?.readyState !== wrapper_default.OPEN) {
12706
+ const ws = this.ws;
12707
+ if (ws?.readyState !== wrapper_default.OPEN) {
12655
12708
  return;
12656
12709
  }
12657
12710
  const idleMs = Date.now() - this.lastInboundAt;
@@ -12660,19 +12713,16 @@ var RelayClient = class {
12660
12713
  this.opts.logger.warn(
12661
12714
  `Relay tunnel: heartbeat timeout, no inbound activity for ${idleMs}ms (threshold=${timeoutMs}ms)`
12662
12715
  );
12663
- try {
12664
- this.ws.terminate();
12665
- } catch {
12666
- }
12716
+ this.forceReconnectFromSocket(ws, `heartbeat-timeout idleMs=${idleMs}`);
12667
12717
  return;
12668
12718
  }
12669
12719
  this.opts.logger.info('Relay tunnel: \u2192 heartbeat "ping"');
12670
12720
  try {
12671
- this.ws.send("ping");
12721
+ ws.send("ping");
12672
12722
  } catch {
12673
12723
  }
12674
12724
  try {
12675
- this.ws.ping();
12725
+ ws.ping();
12676
12726
  } catch {
12677
12727
  }
12678
12728
  }
@@ -12700,6 +12750,34 @@ var RelayClient = class {
12700
12750
  this.ws = null;
12701
12751
  }
12702
12752
  }
12753
+ forceReconnectFromSocket(ws, reason) {
12754
+ if (this.aborted || this.ws !== ws) return;
12755
+ this.stopHeartbeat();
12756
+ this.ws = null;
12757
+ this.writeStatus("disconnected", reason);
12758
+ this.scheduleReconnect();
12759
+ try {
12760
+ if (ws.readyState !== wrapper_default.CLOSED) {
12761
+ ws.terminate();
12762
+ }
12763
+ } catch {
12764
+ }
12765
+ }
12766
+ logSendSkipped(kind, detail) {
12767
+ const now = Date.now();
12768
+ const shouldLog = this.skippedSendLogLastAt === null || now - this.skippedSendLogLastAt >= SEND_SKIPPED_LOG_INTERVAL_MS;
12769
+ if (!shouldLog) {
12770
+ this.skippedSendLogSuppressed++;
12771
+ return;
12772
+ }
12773
+ const suppressedSuffix = this.skippedSendLogSuppressed > 0 ? `, suppressed=${this.skippedSendLogSuppressed}` : "";
12774
+ const detailSuffix = detail ? `, ${detail}` : "";
12775
+ this.opts.logger.warn(
12776
+ `Relay tunnel: \u25B6 ${kind} skipped, ws not open (readyState=${this.ws?.readyState ?? "null"}${detailSuffix}${suppressedSuffix})`
12777
+ );
12778
+ this.skippedSendLogLastAt = now;
12779
+ this.skippedSendLogSuppressed = 0;
12780
+ }
12703
12781
  scheduleReconnect() {
12704
12782
  if (this.aborted) return;
12705
12783
  if (this.reconnectTimer) {
@@ -12727,36 +12805,6 @@ var RelayClient = class {
12727
12805
 
12728
12806
  // src/tunnel/proxy.ts
12729
12807
  var import_node_crypto5 = require("crypto");
12730
-
12731
- // src/tunnel/utils.ts
12732
- function previewText2(text, max = 200) {
12733
- if (!text) return "";
12734
- return text.length <= max ? text : `${text.substring(0, max)}\u2026`;
12735
- }
12736
- function findHeaderValue(headers, key) {
12737
- if (!headers) return void 0;
12738
- const lowerKey = key.toLowerCase();
12739
- for (const [headerKey, headerValue] of Object.entries(headers)) {
12740
- if (headerKey.toLowerCase() === lowerKey) {
12741
- return headerValue;
12742
- }
12743
- }
12744
- return void 0;
12745
- }
12746
- function summarizeRequestHeaders(headers) {
12747
- const contentType = findHeaderValue(headers, "content-type");
12748
- const requestId = findHeaderValue(headers, "x-request-id");
12749
- const parts = [];
12750
- if (contentType) {
12751
- parts.push(`contentType=${contentType}`);
12752
- }
12753
- if (requestId) {
12754
- parts.push(`xRequestId=${previewText2(requestId, 120)}`);
12755
- }
12756
- return parts.length ? `, ${parts.join(", ")}` : "";
12757
- }
12758
-
12759
- // src/tunnel/proxy.ts
12760
12808
  init_host();
12761
12809
 
12762
12810
  // src/tunnel/device-identity.ts
@@ -12995,7 +13043,7 @@ async function handleHttpRequest(opts, frame) {
12995
13043
  localHeaders[RELAY_INTERNAL_HTTP_HEADER] = "1";
12996
13044
  const authAttempts = buildLocalGatewayAuthAttempts(opts, localHeaders);
12997
13045
  opts.logger.info(
12998
- `TunnelProxy: HTTP id=${frame.id} ${frame.method} ${frame.path} \u2192 ${url.toString()}${summarizeRequestHeaders(frame.headers)}, authAttempts=${authAttempts.map((a) => a.label).join(" -> ")}, body=${previewText2(frame.body)}`
13046
+ `TunnelProxy: HTTP id=${frame.id} ${frame.method} ${frame.path} \u2192 ${url.toString()}${summarizeRequestHeaders(frame.headers)}, authAttempts=${authAttempts.map((a) => a.label).join(" -> ")}, body=${previewText(frame.body)}`
12999
13047
  );
13000
13048
  try {
13001
13049
  for (let attemptIndex = 0; attemptIndex < authAttempts.length; attemptIndex++) {
@@ -13012,7 +13060,7 @@ async function handleHttpRequest(opts, frame) {
13012
13060
  if (res.status === 401 && hasFallback) {
13013
13061
  const body = await res.text();
13014
13062
  opts.logger.warn(
13015
- `TunnelProxy: HTTP id=${frame.id} local gateway auth via ${attempt.label} returned 401 after ${Date.now() - startedAtMs}ms, retrying next credential${body ? `, body=${previewText2(body)}` : ""}`
13063
+ `TunnelProxy: HTTP id=${frame.id} local gateway auth via ${attempt.label} returned 401 after ${Date.now() - startedAtMs}ms, retrying next credential${body ? `, body=${previewText(body)}` : ""}`
13016
13064
  );
13017
13065
  continue;
13018
13066
  }
@@ -13053,7 +13101,7 @@ async function sendHttpResponse(opts, params) {
13053
13101
  }
13054
13102
  const body = await res.text();
13055
13103
  opts.logger.info(
13056
- `TunnelProxy: HTTP id=${frameId} response body=${previewText2(body)}`
13104
+ `TunnelProxy: HTTP id=${frameId} response body=${previewText(body)}`
13057
13105
  );
13058
13106
  const headers = {};
13059
13107
  res.headers.forEach((value, key) => {
@@ -13678,7 +13726,7 @@ var TunnelProxy = class {
13678
13726
  return;
13679
13727
  }
13680
13728
  this.opts.logger.error(
13681
- `TunnelProxy: RPC WS handshake failed (pending=${this.gatewayWsPending.length}): ${previewText2(JSON.stringify(frame.error), 500)}`
13729
+ `TunnelProxy: RPC WS handshake failed (pending=${this.gatewayWsPending.length}): ${previewText(JSON.stringify(frame.error), 500)}`
13682
13730
  );
13683
13731
  ws.close();
13684
13732
  return;
@@ -13854,7 +13902,7 @@ function createTunnelService(opts) {
13854
13902
  JSON.stringify({
13855
13903
  pid: process.pid,
13856
13904
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
13857
- tunnelUrl: opts.tunnelUrl
13905
+ tunnelUrl: redactUrlSecrets(opts.tunnelUrl)
13858
13906
  }) + "\n"
13859
13907
  );
13860
13908
  lockFilePath = filePath;
@@ -13920,7 +13968,7 @@ function createTunnelService(opts) {
13920
13968
  const { logger } = opts;
13921
13969
  const baseStateDir = (0, import_node_path24.join)(ctx.stateDir, "plugins", "phone-notifications");
13922
13970
  logger.info(
13923
- `Relay tunnel: starting (pid=${process.pid}, url=${opts.tunnelUrl}, heartbeat=${opts.heartbeatSec ?? DEFAULT_HEARTBEAT_SEC}s, backoff=${opts.reconnectBackoffMs ?? DEFAULT_RECONNECT_BACKOFF_MS}ms, gateway=${opts.gatewayBaseUrl}, hasGatewayToken=${!!opts.gatewayToken}, hasGatewayPwd=${!!opts.gatewayPassword})`
13971
+ `Relay tunnel: starting (pid=${process.pid}, url=${redactUrlSecrets(opts.tunnelUrl)}, heartbeat=${opts.heartbeatSec ?? DEFAULT_HEARTBEAT_SEC}s, backoff=${opts.reconnectBackoffMs ?? DEFAULT_RECONNECT_BACKOFF_MS}ms, gateway=${opts.gatewayBaseUrl}, hasGatewayToken=${!!opts.gatewayToken}, hasGatewayPwd=${!!opts.gatewayPassword})`
13924
13972
  );
13925
13973
  const statusFilePath = (0, import_node_path24.join)(baseStateDir, "tunnel-status.json");
13926
13974
  const lockPath = (0, import_node_path24.join)(baseStateDir, "relay-tunnel.lock");