@yoooclaw/phone-notifications 1.7.2 → 1.7.5
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/README.md +6 -2
- package/dist/index.js +283 -50
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,13 +50,13 @@ OpenClaw / QClaw 手机通知同步插件:接收手机通知并写入本地 JS
|
|
|
50
50
|
### 通过 OpenClaw CLI 安装(OpenClaw)
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
|
-
openclaw
|
|
53
|
+
openclaw plugins install @yoooclaw/phone-notifications
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
### 更新
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
openclaw
|
|
59
|
+
openclaw plugins update @yoooclaw/phone-notifications
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
### 通过一键脚本安装(推荐给 QClaw,备选给 OpenClaw)
|
|
@@ -321,6 +321,10 @@ openclaw ntf monitor delete boss-alert --yes
|
|
|
321
321
|
### 灯效控制
|
|
322
322
|
|
|
323
323
|
```bash
|
|
324
|
+
|
|
325
|
+
# 启用灯效控制工具
|
|
326
|
+
openclaw ntf light setup
|
|
327
|
+
|
|
324
328
|
# 直接发送灯效指令到硬件设备(需要先 set-api-key)
|
|
325
329
|
openclaw ntf light send \
|
|
326
330
|
--segments '[{"mode":"wave","duration_s":4,"brightness":192,"color":{"r":255,"g":0,"b":0}}]'
|
package/dist/index.js
CHANGED
|
@@ -3647,8 +3647,8 @@ var require_websocket_server = __commonJS({
|
|
|
3647
3647
|
});
|
|
3648
3648
|
|
|
3649
3649
|
// src/index.ts
|
|
3650
|
-
import { readFileSync as
|
|
3651
|
-
import { basename as basename2, dirname as
|
|
3650
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
3651
|
+
import { basename as basename2, dirname as dirname7 } from "path";
|
|
3652
3652
|
|
|
3653
3653
|
// src/light/protocol.ts
|
|
3654
3654
|
var MAX_LIGHT_SEGMENTS = 12;
|
|
@@ -5271,16 +5271,93 @@ function registerLightSend(light) {
|
|
|
5271
5271
|
});
|
|
5272
5272
|
}
|
|
5273
5273
|
|
|
5274
|
+
// src/cli/light-setup-tools.ts
|
|
5275
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
|
|
5276
|
+
import { homedir } from "os";
|
|
5277
|
+
import { dirname as dirname4, join as join8 } from "path";
|
|
5278
|
+
function isObject(value) {
|
|
5279
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
5280
|
+
}
|
|
5281
|
+
function ensureArray(obj, key) {
|
|
5282
|
+
const current = obj[key];
|
|
5283
|
+
if (Array.isArray(current)) return current;
|
|
5284
|
+
const next = [];
|
|
5285
|
+
obj[key] = next;
|
|
5286
|
+
return next;
|
|
5287
|
+
}
|
|
5288
|
+
function resolveConfigPath2() {
|
|
5289
|
+
const fromEnv = process.env.OPENCLAW_CONFIG_PATH?.trim();
|
|
5290
|
+
if (fromEnv) return fromEnv;
|
|
5291
|
+
return join8(homedir(), ".openclaw", "openclaw.json");
|
|
5292
|
+
}
|
|
5293
|
+
function upsertLightControlAlsoAllow(cfg) {
|
|
5294
|
+
if (!isObject(cfg.tools)) cfg.tools = {};
|
|
5295
|
+
const toolsAlsoAllow = ensureArray(cfg.tools, "alsoAllow");
|
|
5296
|
+
const hasGlobal = toolsAlsoAllow.includes("light_control");
|
|
5297
|
+
if (!hasGlobal) toolsAlsoAllow.push("light_control");
|
|
5298
|
+
if (!isObject(cfg.agents)) cfg.agents = {};
|
|
5299
|
+
const agents = cfg.agents;
|
|
5300
|
+
const list = ensureArray(agents, "list");
|
|
5301
|
+
let mainAgent = list.find(
|
|
5302
|
+
(item) => isObject(item) && item.id === "main"
|
|
5303
|
+
);
|
|
5304
|
+
if (!mainAgent) {
|
|
5305
|
+
mainAgent = { id: "main" };
|
|
5306
|
+
list.push(mainAgent);
|
|
5307
|
+
}
|
|
5308
|
+
if (!isObject(mainAgent.tools)) mainAgent.tools = {};
|
|
5309
|
+
const mainAlsoAllow = ensureArray(mainAgent.tools, "alsoAllow");
|
|
5310
|
+
const hasMain = mainAlsoAllow.includes("light_control");
|
|
5311
|
+
if (!hasMain) mainAlsoAllow.push("light_control");
|
|
5312
|
+
return {
|
|
5313
|
+
globalChanged: !hasGlobal,
|
|
5314
|
+
mainAgentChanged: !hasMain
|
|
5315
|
+
};
|
|
5316
|
+
}
|
|
5317
|
+
function registerLightSetupTools(light) {
|
|
5318
|
+
light.command("setup").description("\u81EA\u52A8\u653E\u884C light_control\uFF08\u5199\u5165 tools.alsoAllow \u4E0E agents.main.tools.alsoAllow\uFF09").action(() => {
|
|
5319
|
+
const configPath = resolveConfigPath2();
|
|
5320
|
+
if (!existsSync11(configPath)) {
|
|
5321
|
+
exitError("CONFIG_NOT_FOUND", `\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6: ${configPath}`);
|
|
5322
|
+
}
|
|
5323
|
+
let cfg = {};
|
|
5324
|
+
try {
|
|
5325
|
+
const raw = readFileSync10(configPath, "utf-8");
|
|
5326
|
+
const parsed = JSON.parse(raw);
|
|
5327
|
+
if (isObject(parsed)) cfg = parsed;
|
|
5328
|
+
} catch (err) {
|
|
5329
|
+
exitError("CONFIG_INVALID", `\u8BFB\u53D6/\u89E3\u6790\u914D\u7F6E\u5931\u8D25: ${err?.message ?? String(err)}`);
|
|
5330
|
+
}
|
|
5331
|
+
const result = upsertLightControlAlsoAllow(cfg);
|
|
5332
|
+
try {
|
|
5333
|
+
mkdirSync6(dirname4(configPath), { recursive: true });
|
|
5334
|
+
writeFileSync8(configPath, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
|
|
5335
|
+
} catch (err) {
|
|
5336
|
+
exitError("WRITE_FAILED", `\u5199\u5165\u914D\u7F6E\u5931\u8D25: ${err?.message ?? String(err)}`);
|
|
5337
|
+
}
|
|
5338
|
+
output({
|
|
5339
|
+
ok: true,
|
|
5340
|
+
configPath,
|
|
5341
|
+
changed: result.globalChanged || result.mainAgentChanged,
|
|
5342
|
+
updates: {
|
|
5343
|
+
toolsAlsoAllow: result.globalChanged ? "added" : "already_exists",
|
|
5344
|
+
mainAgentAlsoAllow: result.mainAgentChanged ? "added" : "already_exists"
|
|
5345
|
+
},
|
|
5346
|
+
next: "\u8BF7\u6267\u884C: openclaw gateway restart"
|
|
5347
|
+
});
|
|
5348
|
+
});
|
|
5349
|
+
}
|
|
5350
|
+
|
|
5274
5351
|
// src/cli/tunnel-status.ts
|
|
5275
|
-
import { existsSync as
|
|
5276
|
-
import { join as
|
|
5277
|
-
var STATUS_REL_PATH =
|
|
5352
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
5353
|
+
import { join as join9 } from "path";
|
|
5354
|
+
var STATUS_REL_PATH = join9("plugins", "phone-notifications", "tunnel-status.json");
|
|
5278
5355
|
function readTunnelStatus(ctx) {
|
|
5279
5356
|
if (!ctx.stateDir) return null;
|
|
5280
|
-
const filePath =
|
|
5281
|
-
if (!
|
|
5357
|
+
const filePath = join9(ctx.stateDir, STATUS_REL_PATH);
|
|
5358
|
+
if (!existsSync12(filePath)) return null;
|
|
5282
5359
|
try {
|
|
5283
|
-
return JSON.parse(
|
|
5360
|
+
return JSON.parse(readFileSync11(filePath, "utf-8"));
|
|
5284
5361
|
} catch {
|
|
5285
5362
|
return null;
|
|
5286
5363
|
}
|
|
@@ -5348,17 +5425,17 @@ function registerNtfStoragePath(ntf, ctx) {
|
|
|
5348
5425
|
}
|
|
5349
5426
|
|
|
5350
5427
|
// src/cli/log-search.ts
|
|
5351
|
-
import { existsSync as
|
|
5352
|
-
import { join as
|
|
5428
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12, readdirSync as readdirSync4 } from "fs";
|
|
5429
|
+
import { join as join10 } from "path";
|
|
5353
5430
|
function resolveLogsDir(ctx) {
|
|
5354
5431
|
if (ctx.stateDir) {
|
|
5355
|
-
const dir =
|
|
5432
|
+
const dir = join10(
|
|
5356
5433
|
ctx.stateDir,
|
|
5357
5434
|
"plugins",
|
|
5358
5435
|
"phone-notifications",
|
|
5359
5436
|
"logs"
|
|
5360
5437
|
);
|
|
5361
|
-
if (
|
|
5438
|
+
if (existsSync13(dir)) return dir;
|
|
5362
5439
|
}
|
|
5363
5440
|
return null;
|
|
5364
5441
|
}
|
|
@@ -5373,9 +5450,9 @@ function listLogDateKeys(dir) {
|
|
|
5373
5450
|
return keys.sort().reverse();
|
|
5374
5451
|
}
|
|
5375
5452
|
function collectLogLines(dir, dateKey, keyword, limit, collected) {
|
|
5376
|
-
const filePath =
|
|
5377
|
-
if (!
|
|
5378
|
-
const content =
|
|
5453
|
+
const filePath = join10(dir, `${dateKey}.log`);
|
|
5454
|
+
if (!existsSync13(filePath)) return;
|
|
5455
|
+
const content = readFileSync12(filePath, "utf-8");
|
|
5379
5456
|
const lowerKeyword = keyword?.toLowerCase();
|
|
5380
5457
|
for (const line of content.split("\n")) {
|
|
5381
5458
|
if (collected.length >= limit) return;
|
|
@@ -5441,12 +5518,12 @@ function registerEnvCli(ntf) {
|
|
|
5441
5518
|
}
|
|
5442
5519
|
|
|
5443
5520
|
// src/version.ts
|
|
5444
|
-
import { readFileSync as
|
|
5521
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
5445
5522
|
function readPluginVersion() {
|
|
5446
5523
|
try {
|
|
5447
5524
|
const packageJsonUrl = new URL("../package.json", import.meta.url);
|
|
5448
5525
|
const packageJson = JSON.parse(
|
|
5449
|
-
|
|
5526
|
+
readFileSync13(packageJsonUrl, "utf-8")
|
|
5450
5527
|
);
|
|
5451
5528
|
return packageJson.version ?? "unknown";
|
|
5452
5529
|
} catch {
|
|
@@ -5465,6 +5542,7 @@ function registerAllCli(program, ctx, rootCommandName = "ntf") {
|
|
|
5465
5542
|
registerNtfMonitor(ntf, ctx);
|
|
5466
5543
|
const light = registerLightRules(ntf, ctx);
|
|
5467
5544
|
registerLightSend(light);
|
|
5545
|
+
registerLightSetupTools(light);
|
|
5468
5546
|
registerNtfStoragePath(ntf, ctx);
|
|
5469
5547
|
registerLogSearch(ntf, ctx);
|
|
5470
5548
|
registerTunnelStatus(ntf, ctx);
|
|
@@ -5474,17 +5552,17 @@ function registerAllCli(program, ctx, rootCommandName = "ntf") {
|
|
|
5474
5552
|
// src/tunnel/service.ts
|
|
5475
5553
|
import {
|
|
5476
5554
|
closeSync,
|
|
5477
|
-
mkdirSync as
|
|
5555
|
+
mkdirSync as mkdirSync8,
|
|
5478
5556
|
openSync,
|
|
5479
|
-
readFileSync as
|
|
5557
|
+
readFileSync as readFileSync14,
|
|
5480
5558
|
unlinkSync,
|
|
5481
|
-
writeFileSync as
|
|
5559
|
+
writeFileSync as writeFileSync10
|
|
5482
5560
|
} from "fs";
|
|
5483
|
-
import { dirname as
|
|
5561
|
+
import { dirname as dirname6, join as join11 } from "path";
|
|
5484
5562
|
|
|
5485
5563
|
// src/tunnel/relay-client.ts
|
|
5486
|
-
import { writeFileSync as
|
|
5487
|
-
import { dirname as
|
|
5564
|
+
import { writeFileSync as writeFileSync9, mkdirSync as mkdirSync7 } from "fs";
|
|
5565
|
+
import { dirname as dirname5 } from "path";
|
|
5488
5566
|
|
|
5489
5567
|
// node_modules/.pnpm/ws@8.19.0/node_modules/ws/wrapper.mjs
|
|
5490
5568
|
var import_stream = __toESM(require_stream(), 1);
|
|
@@ -5526,8 +5604,8 @@ var RelayClient = class {
|
|
|
5526
5604
|
lastDisconnectReason
|
|
5527
5605
|
};
|
|
5528
5606
|
try {
|
|
5529
|
-
|
|
5530
|
-
|
|
5607
|
+
mkdirSync7(dirname5(this.opts.statusFilePath), { recursive: true });
|
|
5608
|
+
writeFileSync9(this.opts.statusFilePath, JSON.stringify(info, null, 2));
|
|
5531
5609
|
} catch {
|
|
5532
5610
|
}
|
|
5533
5611
|
}
|
|
@@ -5856,11 +5934,96 @@ function buildDeviceAuthPayload(params) {
|
|
|
5856
5934
|
params.nonce
|
|
5857
5935
|
].join("|");
|
|
5858
5936
|
}
|
|
5859
|
-
function
|
|
5860
|
-
return resolveStateDir();
|
|
5937
|
+
function resolveClientStateDir(stateDir) {
|
|
5938
|
+
return stateDir ?? resolveStateDir();
|
|
5939
|
+
}
|
|
5940
|
+
function ensureDir(filePath) {
|
|
5941
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
5942
|
+
}
|
|
5943
|
+
function resolveIdentityPath(stateDir) {
|
|
5944
|
+
return path.join(stateDir, "identity", "device.json");
|
|
5861
5945
|
}
|
|
5862
|
-
function
|
|
5863
|
-
|
|
5946
|
+
function normalizeDeviceAuthRole(role) {
|
|
5947
|
+
return role.trim();
|
|
5948
|
+
}
|
|
5949
|
+
function normalizeDeviceAuthScopes(scopes) {
|
|
5950
|
+
const out = /* @__PURE__ */ new Set();
|
|
5951
|
+
for (const scope of scopes) {
|
|
5952
|
+
const trimmed = scope.trim();
|
|
5953
|
+
if (trimmed) {
|
|
5954
|
+
out.add(trimmed);
|
|
5955
|
+
}
|
|
5956
|
+
}
|
|
5957
|
+
return [...out].sort();
|
|
5958
|
+
}
|
|
5959
|
+
function resolveDeviceAuthPath(stateDir) {
|
|
5960
|
+
return path.join(stateDir, "identity", "device-auth.json");
|
|
5961
|
+
}
|
|
5962
|
+
function readDeviceAuthStore(filePath) {
|
|
5963
|
+
try {
|
|
5964
|
+
if (!fs.existsSync(filePath)) return null;
|
|
5965
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
5966
|
+
const parsed = JSON.parse(raw);
|
|
5967
|
+
if (parsed?.version !== 1 || typeof parsed.deviceId !== "string") return null;
|
|
5968
|
+
if (!parsed.tokens || typeof parsed.tokens !== "object") return null;
|
|
5969
|
+
return parsed;
|
|
5970
|
+
} catch {
|
|
5971
|
+
return null;
|
|
5972
|
+
}
|
|
5973
|
+
}
|
|
5974
|
+
function writeDeviceAuthStore(filePath, store) {
|
|
5975
|
+
ensureDir(filePath);
|
|
5976
|
+
fs.writeFileSync(filePath, `${JSON.stringify(store, null, 2)}
|
|
5977
|
+
`, {
|
|
5978
|
+
mode: 384
|
|
5979
|
+
});
|
|
5980
|
+
try {
|
|
5981
|
+
fs.chmodSync(filePath, 384);
|
|
5982
|
+
} catch {
|
|
5983
|
+
}
|
|
5984
|
+
}
|
|
5985
|
+
function loadDeviceAuthToken(params) {
|
|
5986
|
+
const store = readDeviceAuthStore(resolveDeviceAuthPath(params.stateDir));
|
|
5987
|
+
if (!store || store.deviceId !== params.deviceId) return null;
|
|
5988
|
+
const entry = store.tokens[normalizeDeviceAuthRole(params.role)];
|
|
5989
|
+
if (!entry || typeof entry.token !== "string") return null;
|
|
5990
|
+
return entry;
|
|
5991
|
+
}
|
|
5992
|
+
function storeDeviceAuthToken(params) {
|
|
5993
|
+
const filePath = resolveDeviceAuthPath(params.stateDir);
|
|
5994
|
+
const existing = readDeviceAuthStore(filePath);
|
|
5995
|
+
const role = normalizeDeviceAuthRole(params.role);
|
|
5996
|
+
const next = {
|
|
5997
|
+
version: 1,
|
|
5998
|
+
deviceId: params.deviceId,
|
|
5999
|
+
tokens: existing && existing.deviceId === params.deviceId && existing.tokens ? { ...existing.tokens } : {}
|
|
6000
|
+
};
|
|
6001
|
+
const entry = {
|
|
6002
|
+
token: params.token,
|
|
6003
|
+
role,
|
|
6004
|
+
scopes: normalizeDeviceAuthScopes(params.scopes),
|
|
6005
|
+
updatedAtMs: Date.now()
|
|
6006
|
+
};
|
|
6007
|
+
next.tokens[role] = entry;
|
|
6008
|
+
writeDeviceAuthStore(filePath, next);
|
|
6009
|
+
return entry;
|
|
6010
|
+
}
|
|
6011
|
+
function clearDeviceAuthToken(params) {
|
|
6012
|
+
const filePath = resolveDeviceAuthPath(params.stateDir);
|
|
6013
|
+
const store = readDeviceAuthStore(filePath);
|
|
6014
|
+
if (!store || store.deviceId !== params.deviceId) return;
|
|
6015
|
+
const role = normalizeDeviceAuthRole(params.role);
|
|
6016
|
+
if (!store.tokens[role]) return;
|
|
6017
|
+
const next = {
|
|
6018
|
+
version: 1,
|
|
6019
|
+
deviceId: store.deviceId,
|
|
6020
|
+
tokens: { ...store.tokens }
|
|
6021
|
+
};
|
|
6022
|
+
delete next.tokens[role];
|
|
6023
|
+
writeDeviceAuthStore(filePath, next);
|
|
6024
|
+
}
|
|
6025
|
+
function loadOrCreateDeviceIdentity(stateDir) {
|
|
6026
|
+
const filePath = resolveIdentityPath(stateDir);
|
|
5864
6027
|
try {
|
|
5865
6028
|
if (fs.existsSync(filePath)) {
|
|
5866
6029
|
const raw = fs.readFileSync(filePath, "utf8");
|
|
@@ -5890,6 +6053,7 @@ function loadOrCreateDeviceIdentity() {
|
|
|
5890
6053
|
...identity,
|
|
5891
6054
|
createdAtMs: Date.now()
|
|
5892
6055
|
};
|
|
6056
|
+
ensureDir(filePath);
|
|
5893
6057
|
fs.writeFileSync(filePath, `${JSON.stringify(stored, null, 2)}
|
|
5894
6058
|
`, {
|
|
5895
6059
|
mode: 384
|
|
@@ -5899,7 +6063,8 @@ function loadOrCreateDeviceIdentity() {
|
|
|
5899
6063
|
var TunnelProxy = class _TunnelProxy {
|
|
5900
6064
|
constructor(opts) {
|
|
5901
6065
|
this.opts = opts;
|
|
5902
|
-
this.
|
|
6066
|
+
this.stateDir = resolveClientStateDir(opts.stateDir);
|
|
6067
|
+
this.deviceIdentity = loadOrCreateDeviceIdentity(this.stateDir);
|
|
5903
6068
|
opts.logger.info(
|
|
5904
6069
|
`TunnelProxy: loaded device identity (deviceId=${this.deviceIdentity.deviceId})`
|
|
5905
6070
|
);
|
|
@@ -5915,6 +6080,7 @@ var TunnelProxy = class _TunnelProxy {
|
|
|
5915
6080
|
gatewayWsPending = [];
|
|
5916
6081
|
/** 设备身份,用于 Gateway connect 握手 */
|
|
5917
6082
|
deviceIdentity;
|
|
6083
|
+
stateDir;
|
|
5918
6084
|
pushGatewayPending(payload, reason) {
|
|
5919
6085
|
this.gatewayWsPending.push(payload);
|
|
5920
6086
|
this.opts.logger.info(
|
|
@@ -5932,6 +6098,65 @@ var TunnelProxy = class _TunnelProxy {
|
|
|
5932
6098
|
password
|
|
5933
6099
|
};
|
|
5934
6100
|
}
|
|
6101
|
+
loadStoredDeviceToken(role) {
|
|
6102
|
+
return loadDeviceAuthToken({
|
|
6103
|
+
stateDir: this.stateDir,
|
|
6104
|
+
deviceId: this.deviceIdentity.deviceId,
|
|
6105
|
+
role
|
|
6106
|
+
})?.token ?? void 0;
|
|
6107
|
+
}
|
|
6108
|
+
buildGatewayConnectAuth(role) {
|
|
6109
|
+
const explicitAuth = this.resolveGatewayConnectAuth();
|
|
6110
|
+
const authPassword = explicitAuth?.password?.trim() || void 0;
|
|
6111
|
+
const explicitGatewayToken = explicitAuth?.token?.trim() || void 0;
|
|
6112
|
+
const deviceToken = explicitGatewayToken ? void 0 : this.loadStoredDeviceToken(role);
|
|
6113
|
+
const authToken = explicitGatewayToken ?? deviceToken;
|
|
6114
|
+
const auth = authToken || authPassword || deviceToken ? {
|
|
6115
|
+
token: authToken,
|
|
6116
|
+
deviceToken,
|
|
6117
|
+
password: authPassword
|
|
6118
|
+
} : void 0;
|
|
6119
|
+
return {
|
|
6120
|
+
auth,
|
|
6121
|
+
authToken,
|
|
6122
|
+
authPassword,
|
|
6123
|
+
deviceToken
|
|
6124
|
+
};
|
|
6125
|
+
}
|
|
6126
|
+
storeIssuedDeviceToken(params) {
|
|
6127
|
+
const token = params.authInfo?.deviceToken;
|
|
6128
|
+
if (typeof token !== "string" || !token.trim()) {
|
|
6129
|
+
return;
|
|
6130
|
+
}
|
|
6131
|
+
const role = typeof params.authInfo?.role === "string" && params.authInfo.role.trim() ? params.authInfo.role.trim() : params.fallbackRole;
|
|
6132
|
+
const scopes = Array.isArray(params.authInfo?.scopes) ? params.authInfo.scopes.filter(
|
|
6133
|
+
(scope) => typeof scope === "string" && !!scope.trim()
|
|
6134
|
+
) : params.fallbackScopes;
|
|
6135
|
+
storeDeviceAuthToken({
|
|
6136
|
+
stateDir: this.stateDir,
|
|
6137
|
+
deviceId: this.deviceIdentity.deviceId,
|
|
6138
|
+
role,
|
|
6139
|
+
token: token.trim(),
|
|
6140
|
+
scopes
|
|
6141
|
+
});
|
|
6142
|
+
}
|
|
6143
|
+
maybeClearStoredDeviceTokenOnMismatch(code, reason) {
|
|
6144
|
+
const explicitAuth = this.resolveGatewayConnectAuth();
|
|
6145
|
+
if (explicitAuth?.token || explicitAuth?.password) {
|
|
6146
|
+
return;
|
|
6147
|
+
}
|
|
6148
|
+
if (code !== 1008 || !reason.toLowerCase().includes("device token mismatch")) {
|
|
6149
|
+
return;
|
|
6150
|
+
}
|
|
6151
|
+
clearDeviceAuthToken({
|
|
6152
|
+
stateDir: this.stateDir,
|
|
6153
|
+
deviceId: this.deviceIdentity.deviceId,
|
|
6154
|
+
role: "operator"
|
|
6155
|
+
});
|
|
6156
|
+
this.opts.logger.warn(
|
|
6157
|
+
`TunnelProxy: cleared stale stored device token after gateway mismatch (deviceId=${this.deviceIdentity.deviceId})`
|
|
6158
|
+
);
|
|
6159
|
+
}
|
|
5935
6160
|
buildLocalGatewayAuthAttempts(baseHeaders) {
|
|
5936
6161
|
const auth = this.resolveGatewayConnectAuth();
|
|
5937
6162
|
const attempts = [];
|
|
@@ -6101,12 +6326,12 @@ var TunnelProxy = class _TunnelProxy {
|
|
|
6101
6326
|
if (frame.type === "event" && frame.event === "connect.challenge") {
|
|
6102
6327
|
const challengeNonce = frame.payload?.nonce ?? "";
|
|
6103
6328
|
const connectRequestId = `tunnel-connect-${randomUUID2()}`;
|
|
6104
|
-
const gatewayAuth = this.resolveGatewayConnectAuth();
|
|
6105
|
-
this.opts.logger.info(
|
|
6106
|
-
`TunnelProxy: received connect.challenge (nonce=${challengeNonce}, connectReqId=${connectRequestId}, hasToken=${!!gatewayAuth?.token}, hasPassword=${!!gatewayAuth?.password}), sending connect request with device identity`
|
|
6107
|
-
);
|
|
6108
6329
|
const role = "operator";
|
|
6109
6330
|
const scopes = ["operator.admin"];
|
|
6331
|
+
const gatewayConnectAuth = this.buildGatewayConnectAuth(role);
|
|
6332
|
+
this.opts.logger.info(
|
|
6333
|
+
`TunnelProxy: received connect.challenge (nonce=${challengeNonce}, connectReqId=${connectRequestId}, hasToken=${!!gatewayConnectAuth.authToken}, hasPassword=${!!gatewayConnectAuth.authPassword}, hasDeviceToken=${!!gatewayConnectAuth.deviceToken}), sending connect request with device identity`
|
|
6334
|
+
);
|
|
6110
6335
|
const signedAtMs = Date.now();
|
|
6111
6336
|
const clientId = "gateway-client";
|
|
6112
6337
|
const clientMode = "backend";
|
|
@@ -6117,7 +6342,7 @@ var TunnelProxy = class _TunnelProxy {
|
|
|
6117
6342
|
role,
|
|
6118
6343
|
scopes,
|
|
6119
6344
|
signedAtMs,
|
|
6120
|
-
token:
|
|
6345
|
+
token: gatewayConnectAuth.authToken ?? null,
|
|
6121
6346
|
nonce: challengeNonce
|
|
6122
6347
|
});
|
|
6123
6348
|
const signature = signDevicePayload(
|
|
@@ -6139,7 +6364,7 @@ var TunnelProxy = class _TunnelProxy {
|
|
|
6139
6364
|
},
|
|
6140
6365
|
role,
|
|
6141
6366
|
scopes,
|
|
6142
|
-
...
|
|
6367
|
+
...gatewayConnectAuth.auth ? { auth: gatewayConnectAuth.auth } : {},
|
|
6143
6368
|
device: {
|
|
6144
6369
|
id: this.deviceIdentity.deviceId,
|
|
6145
6370
|
publicKey: publicKeyRawBase64UrlFromPem(
|
|
@@ -6155,6 +6380,11 @@ var TunnelProxy = class _TunnelProxy {
|
|
|
6155
6380
|
return;
|
|
6156
6381
|
}
|
|
6157
6382
|
if (frame.type === "res" && frame.ok === true && frame.payload?.type === "hello-ok") {
|
|
6383
|
+
this.storeIssuedDeviceToken({
|
|
6384
|
+
fallbackRole: "operator",
|
|
6385
|
+
fallbackScopes: ["operator.admin"],
|
|
6386
|
+
authInfo: frame.payload?.auth
|
|
6387
|
+
});
|
|
6158
6388
|
this.gatewayWsReady = true;
|
|
6159
6389
|
this.gatewayWsConnecting = false;
|
|
6160
6390
|
this.opts.logger.info(
|
|
@@ -6178,14 +6408,16 @@ var TunnelProxy = class _TunnelProxy {
|
|
|
6178
6408
|
ws.on("close", (code, reason) => {
|
|
6179
6409
|
const wasReady = this.gatewayWsReady;
|
|
6180
6410
|
const pendingCount = this.gatewayWsPending.length;
|
|
6411
|
+
const reasonText = reason.toString();
|
|
6181
6412
|
this.opts.logger.info(
|
|
6182
|
-
`TunnelProxy: RPC WS closed by gateway (code=${code}, reason=${
|
|
6413
|
+
`TunnelProxy: RPC WS closed by gateway (code=${code}, reason=${reasonText}, ready=${wasReady}, pending=${pendingCount}, activeWs=${this.wsConnections.size})`
|
|
6183
6414
|
);
|
|
6184
6415
|
if (this.gatewayWs === ws) {
|
|
6185
6416
|
this.gatewayWs = null;
|
|
6186
6417
|
this.gatewayWsReady = false;
|
|
6187
6418
|
}
|
|
6188
6419
|
this.gatewayWsConnecting = false;
|
|
6420
|
+
this.maybeClearStoredDeviceTokenOnMismatch(code, reasonText);
|
|
6189
6421
|
});
|
|
6190
6422
|
ws.on("error", (err) => {
|
|
6191
6423
|
this.opts.logger.warn(
|
|
@@ -6444,7 +6676,7 @@ function createTunnelService(opts) {
|
|
|
6444
6676
|
}
|
|
6445
6677
|
function readLockOwner(filePath) {
|
|
6446
6678
|
try {
|
|
6447
|
-
const parsed = JSON.parse(
|
|
6679
|
+
const parsed = JSON.parse(readFileSync14(filePath, "utf-8"));
|
|
6448
6680
|
return typeof parsed.pid === "number" ? parsed.pid : null;
|
|
6449
6681
|
} catch {
|
|
6450
6682
|
return null;
|
|
@@ -6469,11 +6701,11 @@ function createTunnelService(opts) {
|
|
|
6469
6701
|
}
|
|
6470
6702
|
}
|
|
6471
6703
|
function acquireLock(filePath) {
|
|
6472
|
-
|
|
6704
|
+
mkdirSync8(dirname6(filePath), { recursive: true });
|
|
6473
6705
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
6474
6706
|
try {
|
|
6475
6707
|
const fd = openSync(filePath, "wx", 384);
|
|
6476
|
-
|
|
6708
|
+
writeFileSync10(
|
|
6477
6709
|
fd,
|
|
6478
6710
|
JSON.stringify({
|
|
6479
6711
|
pid: process.pid,
|
|
@@ -6530,12 +6762,12 @@ function createTunnelService(opts) {
|
|
|
6530
6762
|
return;
|
|
6531
6763
|
}
|
|
6532
6764
|
const { logger } = opts;
|
|
6533
|
-
const baseStateDir =
|
|
6765
|
+
const baseStateDir = join11(ctx.stateDir, "plugins", "phone-notifications");
|
|
6534
6766
|
logger.info(
|
|
6535
6767
|
`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})`
|
|
6536
6768
|
);
|
|
6537
|
-
const statusFilePath =
|
|
6538
|
-
const lockPath =
|
|
6769
|
+
const statusFilePath = join11(baseStateDir, "tunnel-status.json");
|
|
6770
|
+
const lockPath = join11(baseStateDir, "relay-tunnel.lock");
|
|
6539
6771
|
if (!acquireLock(lockPath)) {
|
|
6540
6772
|
return;
|
|
6541
6773
|
}
|
|
@@ -6549,6 +6781,7 @@ function createTunnelService(opts) {
|
|
|
6549
6781
|
logger
|
|
6550
6782
|
});
|
|
6551
6783
|
proxy = new TunnelProxy({
|
|
6784
|
+
stateDir: baseStateDir,
|
|
6552
6785
|
gatewayBaseUrl: opts.gatewayBaseUrl,
|
|
6553
6786
|
gatewayAuthMode: opts.gatewayAuthMode,
|
|
6554
6787
|
gatewayToken: opts.gatewayToken,
|
|
@@ -6583,13 +6816,13 @@ function createTunnelService(opts) {
|
|
|
6583
6816
|
}
|
|
6584
6817
|
|
|
6585
6818
|
// src/logger.ts
|
|
6586
|
-
import { appendFileSync as appendFileSync2, mkdirSync as
|
|
6587
|
-
import { join as
|
|
6819
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync9 } from "fs";
|
|
6820
|
+
import { join as join12 } from "path";
|
|
6588
6821
|
var PluginFileLogger = class {
|
|
6589
6822
|
constructor(upstream, stateDir) {
|
|
6590
6823
|
this.upstream = upstream;
|
|
6591
|
-
this.logsDir =
|
|
6592
|
-
|
|
6824
|
+
this.logsDir = join12(stateDir, "plugins", "phone-notifications", "logs");
|
|
6825
|
+
mkdirSync9(this.logsDir, { recursive: true });
|
|
6593
6826
|
}
|
|
6594
6827
|
logsDir;
|
|
6595
6828
|
info(msg) {
|
|
@@ -6611,7 +6844,7 @@ var PluginFileLogger = class {
|
|
|
6611
6844
|
const line = `${time} [${level}] ${msg}
|
|
6612
6845
|
`;
|
|
6613
6846
|
try {
|
|
6614
|
-
appendFileSync2(
|
|
6847
|
+
appendFileSync2(join12(this.logsDir, `${dateKey}.log`), line);
|
|
6615
6848
|
} catch {
|
|
6616
6849
|
}
|
|
6617
6850
|
}
|
|
@@ -6657,7 +6890,7 @@ function resolveLocalGatewayAuth(params) {
|
|
|
6657
6890
|
const configPath = params.stateDir ? resolveConfigPath(params.stateDir) : void 0;
|
|
6658
6891
|
if (configPath) {
|
|
6659
6892
|
try {
|
|
6660
|
-
const configData = JSON.parse(
|
|
6893
|
+
const configData = JSON.parse(readFileSync15(configPath, "utf-8"));
|
|
6661
6894
|
const rawGatewayAuthMode = trimToUndefined2(configData?.gateway?.auth?.mode);
|
|
6662
6895
|
if (rawGatewayAuthMode === "token" || rawGatewayAuthMode === "password") {
|
|
6663
6896
|
configGatewayAuthMode = rawGatewayAuthMode;
|
|
@@ -6935,7 +7168,7 @@ var index_default = {
|
|
|
6935
7168
|
if (openclawDir) return openclawDir;
|
|
6936
7169
|
if (!workspaceDir) return void 0;
|
|
6937
7170
|
if (basename2(workspaceDir) !== "workspace") return void 0;
|
|
6938
|
-
return
|
|
7171
|
+
return dirname7(workspaceDir);
|
|
6939
7172
|
};
|
|
6940
7173
|
api.registerCli(
|
|
6941
7174
|
(ctx) => {
|