@yoooclaw/phone-notifications 1.5.1 → 1.5.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
@@ -5209,30 +5209,9 @@ var RelayClient = class {
5209
5209
  startHeartbeat() {
5210
5210
  this.stopHeartbeat();
5211
5211
  const intervalMs = this.opts.heartbeatSec * 1e3;
5212
+ this.sendHeartbeat();
5212
5213
  this.heartbeatTimer = setInterval(() => {
5213
- if (this.ws?.readyState === wrapper_default.OPEN) {
5214
- const idleMs = Date.now() - this.lastInboundAt;
5215
- const timeoutMs = this.getHeartbeatTimeoutMs();
5216
- if (idleMs >= timeoutMs) {
5217
- this.opts.logger.warn(
5218
- `Relay tunnel: heartbeat timeout, no inbound activity for ${idleMs}ms (threshold=${timeoutMs}ms)`
5219
- );
5220
- try {
5221
- this.ws.terminate();
5222
- } catch {
5223
- }
5224
- return;
5225
- }
5226
- this.opts.logger.info('Relay tunnel: \u2192 heartbeat "ping"');
5227
- try {
5228
- this.ws.send("ping");
5229
- } catch {
5230
- }
5231
- try {
5232
- this.ws.ping();
5233
- } catch {
5234
- }
5235
- }
5214
+ this.sendHeartbeat();
5236
5215
  }, intervalMs);
5237
5216
  }
5238
5217
  stopHeartbeat() {
@@ -5244,6 +5223,32 @@ var RelayClient = class {
5244
5223
  getHeartbeatTimeoutMs() {
5245
5224
  return this.opts.heartbeatSec * 3 * 1e3;
5246
5225
  }
5226
+ sendHeartbeat() {
5227
+ if (this.ws?.readyState !== wrapper_default.OPEN) {
5228
+ return;
5229
+ }
5230
+ const idleMs = Date.now() - this.lastInboundAt;
5231
+ const timeoutMs = this.getHeartbeatTimeoutMs();
5232
+ if (idleMs >= timeoutMs) {
5233
+ this.opts.logger.warn(
5234
+ `Relay tunnel: heartbeat timeout, no inbound activity for ${idleMs}ms (threshold=${timeoutMs}ms)`
5235
+ );
5236
+ try {
5237
+ this.ws.terminate();
5238
+ } catch {
5239
+ }
5240
+ return;
5241
+ }
5242
+ this.opts.logger.info('Relay tunnel: \u2192 heartbeat "ping"');
5243
+ try {
5244
+ this.ws.send("ping");
5245
+ } catch {
5246
+ }
5247
+ try {
5248
+ this.ws.ping();
5249
+ } catch {
5250
+ }
5251
+ }
5247
5252
  markInboundActivity() {
5248
5253
  this.lastInboundAt = Date.now();
5249
5254
  }
@@ -5408,17 +5413,68 @@ var TunnelProxy = class _TunnelProxy {
5408
5413
  password
5409
5414
  };
5410
5415
  }
5411
- applyLocalGatewayAuthHeaders(headers) {
5416
+ buildLocalGatewayAuthAttempts(baseHeaders) {
5412
5417
  const auth = this.resolveGatewayConnectAuth();
5413
- if (auth?.token) {
5414
- headers.authorization = `Bearer ${auth.token}`;
5415
- delete headers["x-openclaw-password"];
5416
- return;
5418
+ const attempts = [];
5419
+ const seen = /* @__PURE__ */ new Set();
5420
+ const authMode = this.opts.gatewayAuthMode;
5421
+ const addAttempt = (kind, secret) => {
5422
+ if (!secret) return;
5423
+ const dedupeKey = `${kind}:${secret}`;
5424
+ if (seen.has(dedupeKey)) return;
5425
+ seen.add(dedupeKey);
5426
+ const headers = { ...baseHeaders };
5427
+ headers.authorization = `Bearer ${secret}`;
5428
+ if (kind === "password") {
5429
+ headers["x-openclaw-password"] = secret;
5430
+ } else {
5431
+ delete headers["x-openclaw-password"];
5432
+ }
5433
+ attempts.push({
5434
+ label: kind === "token" ? "gateway-token" : "gateway-password",
5435
+ headers
5436
+ });
5437
+ };
5438
+ if (authMode === "password") {
5439
+ addAttempt("password", auth?.password);
5440
+ addAttempt("token", auth?.token);
5441
+ } else {
5442
+ addAttempt("token", auth?.token);
5443
+ addAttempt("password", auth?.password);
5417
5444
  }
5418
- if (auth?.password) {
5419
- headers.authorization = `Bearer ${auth.password}`;
5420
- headers["x-openclaw-password"] = auth.password;
5445
+ if (attempts.length === 0) {
5446
+ attempts.push({
5447
+ label: "no-auth",
5448
+ headers: { ...baseHeaders }
5449
+ });
5421
5450
  }
5451
+ return attempts;
5452
+ }
5453
+ async sendHttpResponse(frameId, res) {
5454
+ const contentType = res.headers.get("content-type") ?? "";
5455
+ const isStreaming = contentType.includes("text/event-stream");
5456
+ this.opts.logger.info(
5457
+ `TunnelProxy: response status=${res.status}, content-type=${contentType}, streaming=${isStreaming}`
5458
+ );
5459
+ if (isStreaming && res.body) {
5460
+ await this.streamResponse(frameId, res);
5461
+ return;
5462
+ }
5463
+ const body = await res.text();
5464
+ this.opts.logger.info(
5465
+ `TunnelProxy: response status=${res.status}, body=${body.substring(0, 200)}`
5466
+ );
5467
+ const headers = {};
5468
+ res.headers.forEach((value, key) => {
5469
+ headers[key] = value;
5470
+ });
5471
+ this.opts.client.send({
5472
+ type: "proxy_response",
5473
+ id: frameId,
5474
+ status: res.status,
5475
+ headers,
5476
+ body
5477
+ });
5422
5478
  }
5423
5479
  /** 处理从 Relay 收到的入站帧 */
5424
5480
  async handleFrame(frame) {
@@ -5647,39 +5703,28 @@ var TunnelProxy = class _TunnelProxy {
5647
5703
  localHeaders[k] = v;
5648
5704
  }
5649
5705
  }
5650
- this.applyLocalGatewayAuthHeaders(localHeaders);
5706
+ const authAttempts = this.buildLocalGatewayAuthAttempts(localHeaders);
5651
5707
  this.opts.logger.info(
5652
5708
  `TunnelProxy: HTTP ${frame.method} ${frame.path} \u2192 ${url.toString()}, body=${frame.body?.substring(0, 200)}`
5653
5709
  );
5654
5710
  try {
5655
- const res = await fetch(url.toString(), {
5656
- method: frame.method,
5657
- headers: localHeaders,
5658
- body: frame.method !== "GET" && frame.method !== "HEAD" ? frame.body : void 0
5659
- });
5660
- const contentType = res.headers.get("content-type") ?? "";
5661
- const isStreaming = contentType.includes("text/event-stream");
5662
- this.opts.logger.info(
5663
- `TunnelProxy: response status=${res.status}, content-type=${contentType}, streaming=${isStreaming}`
5664
- );
5665
- if (isStreaming && res.body) {
5666
- await this.streamResponse(frame.id, res);
5667
- } else {
5668
- const body = await res.text();
5669
- this.opts.logger.info(
5670
- `TunnelProxy: response status=${res.status}, body=${body.substring(0, 200)}`
5671
- );
5672
- const headers = {};
5673
- res.headers.forEach((value, key) => {
5674
- headers[key] = value;
5675
- });
5676
- this.opts.client.send({
5677
- type: "proxy_response",
5678
- id: frame.id,
5679
- status: res.status,
5680
- headers,
5681
- body
5711
+ for (let attemptIndex = 0; attemptIndex < authAttempts.length; attemptIndex++) {
5712
+ const attempt = authAttempts[attemptIndex];
5713
+ const res = await fetch(url.toString(), {
5714
+ method: frame.method,
5715
+ headers: attempt.headers,
5716
+ body: frame.method !== "GET" && frame.method !== "HEAD" ? frame.body : void 0
5682
5717
  });
5718
+ const hasFallback = attemptIndex < authAttempts.length - 1;
5719
+ if (res.status === 401 && hasFallback) {
5720
+ const body = await res.text();
5721
+ this.opts.logger.warn(
5722
+ `TunnelProxy: local gateway auth via ${attempt.label} returned 401, retrying next credential${body ? `, body=${body.substring(0, 200)}` : ""}`
5723
+ );
5724
+ continue;
5725
+ }
5726
+ await this.sendHttpResponse(frame.id, res);
5727
+ return;
5683
5728
  }
5684
5729
  } catch (err) {
5685
5730
  this.opts.client.send({
@@ -5832,6 +5877,8 @@ var TunnelProxy = class _TunnelProxy {
5832
5877
  };
5833
5878
 
5834
5879
  // src/tunnel/service.ts
5880
+ var DEFAULT_HEARTBEAT_SEC = 10;
5881
+ var DEFAULT_RECONNECT_BACKOFF_MS = 2e3;
5835
5882
  function createTunnelService(opts) {
5836
5883
  let client = null;
5837
5884
  let proxy = null;
@@ -5938,7 +5985,7 @@ function createTunnelService(opts) {
5938
5985
  const { logger } = opts;
5939
5986
  const baseStateDir = join9(ctx.stateDir, "plugins", "phone-notifications");
5940
5987
  logger.info(
5941
- `Relay tunnel: starting (pid=${process.pid}, url=${opts.tunnelUrl}, heartbeat=${opts.heartbeatSec ?? 30}s, backoff=${opts.reconnectBackoffMs ?? 2e3}ms, gateway=${opts.gatewayBaseUrl}, hasGatewayToken=${!!opts.gatewayToken}, hasGatewayPwd=${!!opts.gatewayPassword})`
5988
+ `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})`
5942
5989
  );
5943
5990
  const statusFilePath = join9(baseStateDir, "tunnel-status.json");
5944
5991
  const lockPath = join9(baseStateDir, "relay-tunnel.lock");
@@ -5949,13 +5996,14 @@ function createTunnelService(opts) {
5949
5996
  client = new RelayClient({
5950
5997
  tunnelUrl: opts.tunnelUrl,
5951
5998
  token,
5952
- heartbeatSec: opts.heartbeatSec ?? 30,
5953
- reconnectBackoffMs: opts.reconnectBackoffMs ?? 2e3,
5999
+ heartbeatSec: opts.heartbeatSec ?? DEFAULT_HEARTBEAT_SEC,
6000
+ reconnectBackoffMs: opts.reconnectBackoffMs ?? DEFAULT_RECONNECT_BACKOFF_MS,
5954
6001
  statusFilePath,
5955
6002
  logger
5956
6003
  });
5957
6004
  proxy = new TunnelProxy({
5958
6005
  gatewayBaseUrl: opts.gatewayBaseUrl,
6006
+ gatewayAuthMode: opts.gatewayAuthMode,
5959
6007
  gatewayToken: opts.gatewayToken,
5960
6008
  gatewayPassword: opts.gatewayPassword,
5961
6009
  client,
@@ -6047,12 +6095,17 @@ function trimToUndefined(value) {
6047
6095
  function resolveLocalGatewayAuth(params) {
6048
6096
  const envGatewayToken = trimToUndefined(process.env.OPENCLAW_GATEWAY_TOKEN) ?? trimToUndefined(process.env.CLAWDBOT_GATEWAY_TOKEN);
6049
6097
  const envGatewayPassword = trimToUndefined(process.env.OPENCLAW_GATEWAY_PASSWORD) ?? trimToUndefined(process.env.CLAWDBOT_GATEWAY_PASSWORD);
6098
+ let configGatewayAuthMode;
6050
6099
  let configGatewayToken;
6051
6100
  let configGatewayPassword;
6052
6101
  const configPath = params.stateDir ? `${params.stateDir}/openclaw.json` : void 0;
6053
6102
  if (configPath) {
6054
6103
  try {
6055
6104
  const configData = JSON.parse(readFileSync11(configPath, "utf-8"));
6105
+ const rawGatewayAuthMode = trimToUndefined(configData?.gateway?.auth?.mode);
6106
+ if (rawGatewayAuthMode === "token" || rawGatewayAuthMode === "password") {
6107
+ configGatewayAuthMode = rawGatewayAuthMode;
6108
+ }
6056
6109
  configGatewayToken = trimToUndefined(configData?.gateway?.auth?.token);
6057
6110
  configGatewayPassword = trimToUndefined(configData?.gateway?.auth?.password);
6058
6111
  } catch (err) {
@@ -6064,6 +6117,7 @@ function resolveLocalGatewayAuth(params) {
6064
6117
  }
6065
6118
  }
6066
6119
  return {
6120
+ gatewayAuthMode: configGatewayAuthMode,
6067
6121
  gatewayToken: envGatewayToken ?? configGatewayToken,
6068
6122
  gatewayPassword: envGatewayPassword ?? configGatewayPassword
6069
6123
  };
@@ -6097,7 +6151,7 @@ var index_default = {
6097
6151
  const tunnelUrl = "wss://openclaw-service-dev.yoootek.com/message/messages/ws/plugin";
6098
6152
  if (tunnelUrl) {
6099
6153
  const gatewayPort = process.env.OPENCLAW_GATEWAY_PORT || "18789";
6100
- const { gatewayToken, gatewayPassword } = resolveLocalGatewayAuth({
6154
+ const { gatewayAuthMode, gatewayToken, gatewayPassword } = resolveLocalGatewayAuth({
6101
6155
  stateDir: openclawDir,
6102
6156
  logger
6103
6157
  });
@@ -6106,6 +6160,7 @@ var index_default = {
6106
6160
  heartbeatSec: config.relay?.heartbeatSec,
6107
6161
  reconnectBackoffMs: config.relay?.reconnectBackoffMs,
6108
6162
  gatewayBaseUrl: `http://localhost:${gatewayPort}`,
6163
+ gatewayAuthMode,
6109
6164
  gatewayToken,
6110
6165
  gatewayPassword,
6111
6166
  logger