@yoooclaw/phone-notifications 1.7.1 → 1.7.2

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.js CHANGED
@@ -5495,6 +5495,16 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
5495
5495
  var wrapper_default = import_websocket.default;
5496
5496
 
5497
5497
  // src/tunnel/relay-client.ts
5498
+ function previewText(text, max = 500) {
5499
+ return text.length <= max ? text : `${text.substring(0, max)}\u2026`;
5500
+ }
5501
+ function maskSecret(value) {
5502
+ if (!value) return "empty";
5503
+ if (value.length <= 8) {
5504
+ return `${value.slice(0, 2)}\u2026${value.slice(-2)}`;
5505
+ }
5506
+ return `${value.slice(0, 4)}\u2026${value.slice(-4)}`;
5507
+ }
5498
5508
  var RelayClient = class {
5499
5509
  constructor(opts) {
5500
5510
  this.opts = opts;
@@ -5575,8 +5585,9 @@ var RelayClient = class {
5575
5585
  async connect() {
5576
5586
  if (this.aborted) return;
5577
5587
  this.cleanup(true);
5588
+ const rawApiKey = this.opts.apiKey.startsWith("Bearer ") ? this.opts.apiKey.slice("Bearer ".length) : this.opts.apiKey;
5578
5589
  this.opts.logger.info(
5579
- `Relay tunnel: connecting to ${this.opts.tunnelUrl} (attempt=${this.reconnectAttempt}, heartbeat=${this.opts.heartbeatSec}s)`
5590
+ `Relay tunnel: connecting to ${this.opts.tunnelUrl} (attempt=${this.reconnectAttempt}, heartbeat=${this.opts.heartbeatSec}s, apiKey=${maskSecret(rawApiKey)})`
5580
5591
  );
5581
5592
  this.writeStatus("connecting");
5582
5593
  return new Promise((resolve) => {
@@ -5587,7 +5598,6 @@ var RelayClient = class {
5587
5598
  resolve();
5588
5599
  }
5589
5600
  };
5590
- const rawApiKey = this.opts.apiKey.startsWith("Bearer ") ? this.opts.apiKey.slice("Bearer ".length) : this.opts.apiKey;
5591
5601
  const wsUrl = new URL(this.opts.tunnelUrl);
5592
5602
  if (!wsUrl.searchParams.get("apiKey")) {
5593
5603
  wsUrl.searchParams.set("apiKey", rawApiKey);
@@ -5631,8 +5641,9 @@ var RelayClient = class {
5631
5641
  });
5632
5642
  ws.on("close", (code, reason) => {
5633
5643
  const reasonStr = reason.toString();
5644
+ const lastInboundAgoMs = this.lastInboundAt ? Date.now() - this.lastInboundAt : null;
5634
5645
  this.opts.logger.warn(
5635
- `Relay tunnel: disconnected (code=${code}, reason=${reasonStr})`
5646
+ `Relay tunnel: disconnected (code=${code}, reason=${previewText(reasonStr, 200)}, lastInboundAgoMs=${lastInboundAgoMs ?? "N/A"}, reconnectAttempt=${this.reconnectAttempt})`
5636
5647
  );
5637
5648
  if (this.ws === ws) {
5638
5649
  this.stopHeartbeat();
@@ -5643,7 +5654,9 @@ var RelayClient = class {
5643
5654
  settle();
5644
5655
  });
5645
5656
  ws.on("error", (err) => {
5646
- this.opts.logger.error(`Relay tunnel: WebSocket error: ${err.message}`);
5657
+ this.opts.logger.error(
5658
+ `Relay tunnel: WebSocket error: ${err.message} (readyState=${ws.readyState}, reconnectAttempt=${this.reconnectAttempt}, url=${wsUrl.toString()})`
5659
+ );
5647
5660
  settle();
5648
5661
  });
5649
5662
  });
@@ -5654,13 +5667,16 @@ var RelayClient = class {
5654
5667
  if (text === "pong") {
5655
5668
  return;
5656
5669
  }
5657
- const preview = text.length <= 500 ? text : text.substring(0, 500) + "\u2026";
5658
- this.opts.logger.info(`Relay tunnel: \u2605 received message (${text.length} chars): ${preview}`);
5670
+ this.opts.logger.info(
5671
+ `Relay tunnel: \u2605 received message (${text.length} chars): ${previewText(text)}`
5672
+ );
5659
5673
  let frame;
5660
5674
  try {
5661
5675
  frame = JSON.parse(text);
5662
5676
  } catch {
5663
- this.opts.logger.warn("Relay tunnel: received invalid frame, ignoring");
5677
+ this.opts.logger.warn(
5678
+ `Relay tunnel: received invalid frame, ignoring (preview=${previewText(text, 200)})`
5679
+ );
5664
5680
  return;
5665
5681
  }
5666
5682
  this.opts.logger.info(`Relay tunnel: parsed frame type=${frame.type}, id=${"id" in frame ? frame.id : "N/A"}`);
@@ -5775,6 +5791,32 @@ import { randomUUID as randomUUID2 } from "crypto";
5775
5791
  import crypto from "crypto";
5776
5792
  import fs from "fs";
5777
5793
  import path from "path";
5794
+ function previewText2(text, max = 200) {
5795
+ if (!text) return "";
5796
+ return text.length <= max ? text : `${text.substring(0, max)}\u2026`;
5797
+ }
5798
+ function findHeaderValue(headers, key) {
5799
+ if (!headers) return void 0;
5800
+ const lowerKey = key.toLowerCase();
5801
+ for (const [headerKey, headerValue] of Object.entries(headers)) {
5802
+ if (headerKey.toLowerCase() === lowerKey) {
5803
+ return headerValue;
5804
+ }
5805
+ }
5806
+ return void 0;
5807
+ }
5808
+ function summarizeRequestHeaders(headers) {
5809
+ const contentType = findHeaderValue(headers, "content-type");
5810
+ const requestId = findHeaderValue(headers, "x-request-id");
5811
+ const parts = [];
5812
+ if (contentType) {
5813
+ parts.push(`contentType=${contentType}`);
5814
+ }
5815
+ if (requestId) {
5816
+ parts.push(`xRequestId=${previewText2(requestId, 120)}`);
5817
+ }
5818
+ return parts.length ? `, ${parts.join(", ")}` : "";
5819
+ }
5778
5820
  var ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
5779
5821
  function base64UrlEncode(buf) {
5780
5822
  return buf.toString("base64").replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/g, "");
@@ -5873,6 +5915,12 @@ var TunnelProxy = class _TunnelProxy {
5873
5915
  gatewayWsPending = [];
5874
5916
  /** 设备身份,用于 Gateway connect 握手 */
5875
5917
  deviceIdentity;
5918
+ pushGatewayPending(payload, reason) {
5919
+ this.gatewayWsPending.push(payload);
5920
+ this.opts.logger.info(
5921
+ `TunnelProxy: gateway WS pending queue size=${this.gatewayWsPending.length} (${reason})`
5922
+ );
5923
+ }
5876
5924
  resolveGatewayConnectAuth() {
5877
5925
  const token = this.opts.gatewayToken?.trim() || void 0;
5878
5926
  const password = this.opts.gatewayPassword?.trim() || void 0;
@@ -5921,19 +5969,21 @@ var TunnelProxy = class _TunnelProxy {
5921
5969
  }
5922
5970
  return attempts;
5923
5971
  }
5924
- async sendHttpResponse(frameId, res) {
5972
+ async sendHttpResponse(params) {
5973
+ const { frameId, method, path: path2, authLabel, startedAtMs, res } = params;
5925
5974
  const contentType = res.headers.get("content-type") ?? "";
5926
5975
  const isStreaming = contentType.includes("text/event-stream");
5976
+ const elapsedMs = Date.now() - startedAtMs;
5927
5977
  this.opts.logger.info(
5928
- `TunnelProxy: response status=${res.status}, content-type=${contentType}, streaming=${isStreaming}`
5978
+ `TunnelProxy: HTTP id=${frameId} ${method} ${path2} <= ${res.status} (${elapsedMs}ms, auth=${authLabel}, content-type=${contentType}, streaming=${isStreaming})`
5929
5979
  );
5930
5980
  if (isStreaming && res.body) {
5931
- await this.streamResponse(frameId, res);
5981
+ await this.streamResponse(frameId, res, startedAtMs);
5932
5982
  return;
5933
5983
  }
5934
5984
  const body = await res.text();
5935
5985
  this.opts.logger.info(
5936
- `TunnelProxy: response status=${res.status}, body=${body.substring(0, 200)}`
5986
+ `TunnelProxy: HTTP id=${frameId} response body=${previewText2(body)}`
5937
5987
  );
5938
5988
  const headers = {};
5939
5989
  res.headers.forEach((value, key) => {
@@ -5982,7 +6032,7 @@ var TunnelProxy = class _TunnelProxy {
5982
6032
  if (this.gatewayWsReady && this.gatewayWs?.readyState === wrapper_default.OPEN) {
5983
6033
  this.gatewayWs.send(payload);
5984
6034
  } else {
5985
- this.gatewayWsPending.push(payload);
6035
+ this.pushGatewayPending(payload, "raw frame queued before gateway WS ready");
5986
6036
  }
5987
6037
  }
5988
6038
  /** 清理所有代理的 WebSocket 连接 */
@@ -6016,12 +6066,14 @@ var TunnelProxy = class _TunnelProxy {
6016
6066
  this.gatewayWsConnecting = true;
6017
6067
  this.gatewayWsReady = false;
6018
6068
  const wsUrl = this.opts.gatewayBaseUrl.replace(/^http/, "ws");
6019
- this.opts.logger.info(`TunnelProxy: RPC WS connecting to gateway ${wsUrl}`);
6069
+ this.opts.logger.info(
6070
+ `TunnelProxy: RPC WS connecting to gateway ${wsUrl} (pending=${this.gatewayWsPending.length})`
6071
+ );
6020
6072
  const ws = new wrapper_default(wsUrl);
6021
6073
  ws.on("open", () => {
6022
6074
  this.gatewayWs = ws;
6023
6075
  this.opts.logger.info(
6024
- "TunnelProxy: RPC WS tcp connected, waiting for connect.challenge"
6076
+ `TunnelProxy: RPC WS tcp connected, waiting for connect.challenge (pending=${this.gatewayWsPending.length})`
6025
6077
  );
6026
6078
  });
6027
6079
  ws.on("message", (data) => {
@@ -6048,15 +6100,16 @@ var TunnelProxy = class _TunnelProxy {
6048
6100
  }
6049
6101
  if (frame.type === "event" && frame.event === "connect.challenge") {
6050
6102
  const challengeNonce = frame.payload?.nonce ?? "";
6103
+ const connectRequestId = `tunnel-connect-${randomUUID2()}`;
6104
+ const gatewayAuth = this.resolveGatewayConnectAuth();
6051
6105
  this.opts.logger.info(
6052
- `TunnelProxy: received connect.challenge (nonce=${challengeNonce}), sending connect request with device identity`
6106
+ `TunnelProxy: received connect.challenge (nonce=${challengeNonce}, connectReqId=${connectRequestId}, hasToken=${!!gatewayAuth?.token}, hasPassword=${!!gatewayAuth?.password}), sending connect request with device identity`
6053
6107
  );
6054
6108
  const role = "operator";
6055
6109
  const scopes = ["operator.admin"];
6056
6110
  const signedAtMs = Date.now();
6057
6111
  const clientId = "gateway-client";
6058
6112
  const clientMode = "backend";
6059
- const gatewayAuth = this.resolveGatewayConnectAuth();
6060
6113
  const authPayload = buildDeviceAuthPayload({
6061
6114
  deviceId: this.deviceIdentity.deviceId,
6062
6115
  clientId,
@@ -6073,7 +6126,7 @@ var TunnelProxy = class _TunnelProxy {
6073
6126
  );
6074
6127
  const connectReq = {
6075
6128
  type: "req",
6076
- id: `tunnel-connect-${randomUUID2()}`,
6129
+ id: connectRequestId,
6077
6130
  method: "connect",
6078
6131
  params: {
6079
6132
  minProtocol: 3,
@@ -6115,7 +6168,7 @@ var TunnelProxy = class _TunnelProxy {
6115
6168
  }
6116
6169
  if (frame.type === "res" && frame.ok === false && !this.gatewayWsReady) {
6117
6170
  this.opts.logger.error(
6118
- `TunnelProxy: RPC WS handshake failed: ${JSON.stringify(frame.error)}`
6171
+ `TunnelProxy: RPC WS handshake failed (pending=${this.gatewayWsPending.length}): ${previewText2(JSON.stringify(frame.error), 500)}`
6119
6172
  );
6120
6173
  ws.close();
6121
6174
  return;
@@ -6123,8 +6176,10 @@ var TunnelProxy = class _TunnelProxy {
6123
6176
  this.opts.client.sendRaw(text);
6124
6177
  });
6125
6178
  ws.on("close", (code, reason) => {
6179
+ const wasReady = this.gatewayWsReady;
6180
+ const pendingCount = this.gatewayWsPending.length;
6126
6181
  this.opts.logger.info(
6127
- `TunnelProxy: RPC WS closed by gateway (code=${code}, reason=${reason.toString()})`
6182
+ `TunnelProxy: RPC WS closed by gateway (code=${code}, reason=${reason.toString()}, ready=${wasReady}, pending=${pendingCount}, activeWs=${this.wsConnections.size})`
6128
6183
  );
6129
6184
  if (this.gatewayWs === ws) {
6130
6185
  this.gatewayWs = null;
@@ -6133,7 +6188,9 @@ var TunnelProxy = class _TunnelProxy {
6133
6188
  this.gatewayWsConnecting = false;
6134
6189
  });
6135
6190
  ws.on("error", (err) => {
6136
- this.opts.logger.warn(`TunnelProxy: RPC WS error: ${err.message}`);
6191
+ this.opts.logger.warn(
6192
+ `TunnelProxy: RPC WS error: ${err.message} (ready=${this.gatewayWsReady}, pending=${this.gatewayWsPending.length}, activeWs=${this.wsConnections.size})`
6193
+ );
6137
6194
  this.gatewayWsConnecting = false;
6138
6195
  if (this.gatewayWs === ws) {
6139
6196
  this.gatewayWs = null;
@@ -6154,7 +6211,10 @@ var TunnelProxy = class _TunnelProxy {
6154
6211
  this.opts.logger.info(
6155
6212
  `TunnelProxy: req id=${frame.id} queued, gateway WS not ready yet`
6156
6213
  );
6157
- this.gatewayWsPending.push(payload);
6214
+ this.pushGatewayPending(
6215
+ payload,
6216
+ `req id=${frame.id} queued before gateway WS handshake`
6217
+ );
6158
6218
  }
6159
6219
  }
6160
6220
  // ─── 路径映射 ───
@@ -6166,7 +6226,9 @@ var TunnelProxy = class _TunnelProxy {
6166
6226
  }
6167
6227
  // ─── HTTP 请求代理 ───
6168
6228
  async handleHttpRequest(frame) {
6169
- const url = new URL(this.mapPath(frame.path), this.opts.gatewayBaseUrl);
6229
+ const mappedPath = this.mapPath(frame.path);
6230
+ const url = new URL(mappedPath, this.opts.gatewayBaseUrl);
6231
+ const startedAtMs = Date.now();
6170
6232
  const localHeaders = {};
6171
6233
  for (const [k, v] of Object.entries(frame.headers ?? {})) {
6172
6234
  const lower = k.toLowerCase();
@@ -6176,11 +6238,14 @@ var TunnelProxy = class _TunnelProxy {
6176
6238
  }
6177
6239
  const authAttempts = this.buildLocalGatewayAuthAttempts(localHeaders);
6178
6240
  this.opts.logger.info(
6179
- `TunnelProxy: HTTP ${frame.method} ${frame.path} \u2192 ${url.toString()}, body=${frame.body?.substring(0, 200)}`
6241
+ `TunnelProxy: HTTP id=${frame.id} ${frame.method} ${frame.path} \u2192 ${url.toString()}${summarizeRequestHeaders(frame.headers)}, authAttempts=${authAttempts.map((attempt) => attempt.label).join(" -> ")}, body=${previewText2(frame.body)}`
6180
6242
  );
6181
6243
  try {
6182
6244
  for (let attemptIndex = 0; attemptIndex < authAttempts.length; attemptIndex++) {
6183
6245
  const attempt = authAttempts[attemptIndex];
6246
+ this.opts.logger.info(
6247
+ `TunnelProxy: HTTP id=${frame.id} attempt ${attemptIndex + 1}/${authAttempts.length} auth=${attempt.label}`
6248
+ );
6184
6249
  const res = await fetch(url.toString(), {
6185
6250
  method: frame.method,
6186
6251
  headers: attempt.headers,
@@ -6190,23 +6255,34 @@ var TunnelProxy = class _TunnelProxy {
6190
6255
  if (res.status === 401 && hasFallback) {
6191
6256
  const body = await res.text();
6192
6257
  this.opts.logger.warn(
6193
- `TunnelProxy: local gateway auth via ${attempt.label} returned 401, retrying next credential${body ? `, body=${body.substring(0, 200)}` : ""}`
6258
+ `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)}` : ""}`
6194
6259
  );
6195
6260
  continue;
6196
6261
  }
6197
- await this.sendHttpResponse(frame.id, res);
6262
+ await this.sendHttpResponse({
6263
+ frameId: frame.id,
6264
+ method: frame.method,
6265
+ path: mappedPath,
6266
+ authLabel: attempt.label,
6267
+ startedAtMs,
6268
+ res
6269
+ });
6198
6270
  return;
6199
6271
  }
6200
6272
  } catch (err) {
6273
+ const message = err instanceof Error ? err.message : String(err);
6274
+ this.opts.logger.error(
6275
+ `TunnelProxy: HTTP id=${frame.id} ${frame.method} ${mappedPath} failed after ${Date.now() - startedAtMs}ms: ${message}`
6276
+ );
6201
6277
  this.opts.client.send({
6202
6278
  type: "proxy_error",
6203
6279
  id: frame.id,
6204
6280
  status: 502,
6205
- message: `gateway unreachable: ${err instanceof Error ? err.message : String(err)}`
6281
+ message: `gateway unreachable: ${message}`
6206
6282
  });
6207
6283
  }
6208
6284
  }
6209
- async streamResponse(requestId, res) {
6285
+ async streamResponse(requestId, res, startedAtMs) {
6210
6286
  const reader = res.body.getReader();
6211
6287
  const decoder = new TextDecoder();
6212
6288
  let chunkCount = 0;
@@ -6228,7 +6304,7 @@ var TunnelProxy = class _TunnelProxy {
6228
6304
  });
6229
6305
  }
6230
6306
  this.opts.logger.info(
6231
- `TunnelProxy: stream end id=${requestId}, total chunks=${chunkCount}`
6307
+ `TunnelProxy: stream end id=${requestId}, total chunks=${chunkCount}, totalElapsedMs=${Date.now() - startedAtMs}`
6232
6308
  );
6233
6309
  this.opts.client.send({
6234
6310
  type: "stream",
@@ -6238,7 +6314,7 @@ var TunnelProxy = class _TunnelProxy {
6238
6314
  });
6239
6315
  } catch (err) {
6240
6316
  this.opts.logger.error(
6241
- `TunnelProxy: stream error id=${requestId} after ${chunkCount} chunks: ${err instanceof Error ? err.message : String(err)}`
6317
+ `TunnelProxy: stream error id=${requestId} after ${chunkCount} chunks and ${Date.now() - startedAtMs}ms: ${err instanceof Error ? err.message : String(err)}`
6242
6318
  );
6243
6319
  this.opts.client.send({
6244
6320
  type: "proxy_error",