@yoooclaw/phone-notifications 1.9.0 → 1.9.1-beta.1

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
@@ -6261,6 +6261,7 @@ var RelayClient = class {
6261
6261
 
6262
6262
  // src/tunnel/proxy.ts
6263
6263
  import { randomUUID as randomUUID2 } from "crypto";
6264
+ import { approveDevicePairing } from "openclaw/plugin-sdk";
6264
6265
 
6265
6266
  // src/tunnel/utils.ts
6266
6267
  function previewText2(text, max = 200) {
@@ -6757,10 +6758,12 @@ var WsProxy = class {
6757
6758
  };
6758
6759
 
6759
6760
  // src/tunnel/proxy.ts
6761
+ var MAX_AUTO_PAIRING_APPROVALS = 3;
6760
6762
  var TunnelProxy = class {
6761
6763
  constructor(opts) {
6762
6764
  this.opts = opts;
6763
6765
  this.stateDir = resolveClientStateDir(opts.stateDir);
6766
+ this.hostStateDir = opts.hostStateDir ?? resolveStateDir();
6764
6767
  this.deviceIdentity = loadOrCreateDeviceIdentity(this.stateDir);
6765
6768
  opts.logger.info(
6766
6769
  `TunnelProxy: loaded device identity (deviceId=${this.deviceIdentity.deviceId})`
@@ -6776,11 +6779,16 @@ var TunnelProxy = class {
6776
6779
  gatewayWsConnecting = false;
6777
6780
  /** 握手是否已完成(收到 hello-ok) */
6778
6781
  gatewayWsReady = false;
6782
+ /** 收到本地自动配对成功后,在 close 回调里触发重连 */
6783
+ gatewayWsReconnectRequested = false;
6784
+ /** 防止配对失败时无限重连 */
6785
+ gatewayWsAutoPairingApprovals = 0;
6779
6786
  /** 等待 Gateway WS 握手完成后发送的帧队列 */
6780
6787
  gatewayWsPending = [];
6781
6788
  /** 设备身份,用于 Gateway connect 握手 */
6782
6789
  deviceIdentity;
6783
6790
  stateDir;
6791
+ hostStateDir;
6784
6792
  wsProxy;
6785
6793
  // ─── Frame dispatcher ───
6786
6794
  /** 处理从 Relay 收到的入站帧 */
@@ -6825,6 +6833,10 @@ var TunnelProxy = class {
6825
6833
  }
6826
6834
  this.gatewayWs = null;
6827
6835
  }
6836
+ this.gatewayWsReady = false;
6837
+ this.gatewayWsConnecting = false;
6838
+ this.gatewayWsReconnectRequested = false;
6839
+ this.gatewayWsAutoPairingApprovals = 0;
6828
6840
  this.gatewayWsPending = [];
6829
6841
  }
6830
6842
  // ─── Gateway RPC WebSocket ───
@@ -6913,6 +6925,53 @@ var TunnelProxy = class {
6913
6925
  `TunnelProxy: cleared stale stored device token after gateway mismatch (deviceId=${this.deviceIdentity.deviceId})`
6914
6926
  );
6915
6927
  }
6928
+ async maybeAutoApproveGatewayPairing(frame) {
6929
+ const errorCode = typeof frame?.error?.code === "string" ? frame.error.code : void 0;
6930
+ const detailsCode = typeof frame?.error?.details?.code === "string" ? frame.error.details.code : void 0;
6931
+ if (errorCode !== "NOT_PAIRED" && detailsCode !== "PAIRING_REQUIRED") {
6932
+ return false;
6933
+ }
6934
+ const requestId = typeof frame?.error?.details?.requestId === "string" ? frame.error.details.requestId.trim() : "";
6935
+ const reason = typeof frame?.error?.details?.reason === "string" ? frame.error.details.reason.trim() : "";
6936
+ if (!requestId) {
6937
+ this.opts.logger.warn(
6938
+ "TunnelProxy: gateway pairing required but requestId missing; unable to auto-approve"
6939
+ );
6940
+ return false;
6941
+ }
6942
+ if (reason && reason !== "not-paired" && reason !== "scope-upgrade") {
6943
+ this.opts.logger.warn(
6944
+ `TunnelProxy: gateway pairing required with unsupported reason=${reason}; skipping auto-approve`
6945
+ );
6946
+ return false;
6947
+ }
6948
+ if (this.gatewayWsAutoPairingApprovals >= MAX_AUTO_PAIRING_APPROVALS) {
6949
+ this.opts.logger.warn(
6950
+ `TunnelProxy: reached auto-pairing retry limit (${MAX_AUTO_PAIRING_APPROVALS}); requestId=${requestId}`
6951
+ );
6952
+ return false;
6953
+ }
6954
+ try {
6955
+ const approved = await approveDevicePairing(requestId, this.hostStateDir);
6956
+ if (!approved) {
6957
+ this.opts.logger.warn(
6958
+ `TunnelProxy: gateway pairing request ${requestId} not found in host state ${this.hostStateDir}`
6959
+ );
6960
+ return false;
6961
+ }
6962
+ this.gatewayWsAutoPairingApprovals += 1;
6963
+ this.gatewayWsReconnectRequested = true;
6964
+ this.opts.logger.info(
6965
+ `TunnelProxy: auto-approved local gateway pairing request ${requestId} (reason=${reason || "not-paired"}, hostStateDir=${this.hostStateDir}, approval=${this.gatewayWsAutoPairingApprovals}/${MAX_AUTO_PAIRING_APPROVALS})`
6966
+ );
6967
+ return true;
6968
+ } catch (err) {
6969
+ this.opts.logger.warn(
6970
+ `TunnelProxy: failed to auto-approve gateway pairing request ${requestId}: ${err?.message ?? String(err)}`
6971
+ );
6972
+ return false;
6973
+ }
6974
+ }
6916
6975
  // ─── Gateway RPC WebSocket lifecycle ───
6917
6976
  /** 确保到本地 Gateway 的 RPC WebSocket 已连接且握手完成 */
6918
6977
  ensureGatewayWs() {
@@ -6932,7 +6991,7 @@ var TunnelProxy = class {
6932
6991
  `TunnelProxy: RPC WS tcp connected, waiting for connect.challenge (pending=${this.gatewayWsPending.length})`
6933
6992
  );
6934
6993
  });
6935
- ws.on("message", (data) => {
6994
+ ws.on("message", async (data) => {
6936
6995
  let text;
6937
6996
  if (typeof data === "string") {
6938
6997
  text = data;
@@ -6964,6 +7023,8 @@ var TunnelProxy = class {
6964
7023
  fallbackScopes: ["operator.admin"],
6965
7024
  authInfo: frame.payload?.auth
6966
7025
  });
7026
+ this.gatewayWsAutoPairingApprovals = 0;
7027
+ this.gatewayWsReconnectRequested = false;
6967
7028
  this.gatewayWsReady = true;
6968
7029
  this.gatewayWsConnecting = false;
6969
7030
  this.opts.logger.info(
@@ -6976,6 +7037,20 @@ var TunnelProxy = class {
6976
7037
  return;
6977
7038
  }
6978
7039
  if (frame.type === "res" && frame.ok === false && !this.gatewayWsReady) {
7040
+ const autoApproved = await this.maybeAutoApproveGatewayPairing(frame);
7041
+ if (autoApproved) {
7042
+ const wsAlreadyClosed = ws.readyState === wrapper_default.CLOSING || ws.readyState === wrapper_default.CLOSED || this.gatewayWs !== ws;
7043
+ this.opts.logger.info(
7044
+ `TunnelProxy: RPC WS handshake requested local pairing approval, reconnecting${wsAlreadyClosed ? " immediately" : " after close"} (pending=${this.gatewayWsPending.length})`
7045
+ );
7046
+ if (wsAlreadyClosed) {
7047
+ this.gatewayWsReconnectRequested = false;
7048
+ queueMicrotask(() => this.ensureGatewayWs());
7049
+ return;
7050
+ }
7051
+ ws.close(1e3, "pairing approved, reconnecting");
7052
+ return;
7053
+ }
6979
7054
  this.opts.logger.error(
6980
7055
  `TunnelProxy: RPC WS handshake failed (pending=${this.gatewayWsPending.length}): ${previewText2(JSON.stringify(frame.error), 500)}`
6981
7056
  );
@@ -6988,6 +7063,7 @@ var TunnelProxy = class {
6988
7063
  const wasReady = this.gatewayWsReady;
6989
7064
  const pendingCount = this.gatewayWsPending.length;
6990
7065
  const reasonText = reason.toString();
7066
+ const shouldReconnect = this.gatewayWsReconnectRequested && pendingCount > 0;
6991
7067
  this.opts.logger.info(
6992
7068
  `TunnelProxy: RPC WS closed by gateway (code=${code}, reason=${reasonText}, ready=${wasReady}, pending=${pendingCount}, activeWs=${this.wsProxy.activeCount})`
6993
7069
  );
@@ -6997,6 +7073,13 @@ var TunnelProxy = class {
6997
7073
  }
6998
7074
  this.gatewayWsConnecting = false;
6999
7075
  this.maybeClearStoredDeviceTokenOnMismatch(code, reasonText);
7076
+ if (shouldReconnect) {
7077
+ this.gatewayWsReconnectRequested = false;
7078
+ this.opts.logger.info(
7079
+ `TunnelProxy: retrying RPC WS after local pairing approval (pending=${this.gatewayWsPending.length})`
7080
+ );
7081
+ queueMicrotask(() => this.ensureGatewayWs());
7082
+ }
7000
7083
  });
7001
7084
  ws.on("error", (err) => {
7002
7085
  this.opts.logger.warn(
@@ -7193,6 +7276,7 @@ function createTunnelService(opts) {
7193
7276
  });
7194
7277
  proxy = new TunnelProxy({
7195
7278
  stateDir: baseStateDir,
7279
+ hostStateDir: ctx.stateDir,
7196
7280
  gatewayBaseUrl: opts.gatewayBaseUrl,
7197
7281
  gatewayAuthMode: opts.gatewayAuthMode,
7198
7282
  gatewayToken: opts.gatewayToken,