mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 01:31:08 +00:00
refactor: dedupe device pair readers
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
clearDeviceBootstrapTokens,
|
||||
definePluginEntry,
|
||||
@@ -139,7 +140,7 @@ const QR_CHANNEL_SENDERS: Record<string, QrChannelSender> = {
|
||||
};
|
||||
|
||||
function normalizeUrl(raw: string, schemeFallback: "ws" | "wss"): string | null {
|
||||
const candidate = raw.trim();
|
||||
const candidate = normalizeOptionalString(raw);
|
||||
if (!candidate) {
|
||||
return null;
|
||||
}
|
||||
@@ -147,7 +148,7 @@ function normalizeUrl(raw: string, schemeFallback: "ws" | "wss"): string | null
|
||||
if (parsedUrl) {
|
||||
return parsedUrl;
|
||||
}
|
||||
const hostPort = candidate.split("/", 1)[0]?.trim() ?? "";
|
||||
const hostPort = normalizeOptionalString(candidate.split("/", 1)[0]) ?? "";
|
||||
return hostPort ? `${schemeFallback}://${hostPort}` : null;
|
||||
}
|
||||
|
||||
@@ -230,7 +231,7 @@ function pickMatchingIPv4(predicate: (address: string) => boolean): string | nul
|
||||
if (!entry || entry.internal || !isIpv4) {
|
||||
continue;
|
||||
}
|
||||
const address = entry.address?.trim() ?? "";
|
||||
const address = normalizeOptionalString(entry.address) ?? "";
|
||||
if (!address) {
|
||||
continue;
|
||||
}
|
||||
@@ -281,10 +282,7 @@ function resolveAuthLabel(cfg: OpenClawPluginApi["config"]): ResolveAuthLabelRes
|
||||
|
||||
function pickFirstDefined(candidates: Array<unknown>): string | null {
|
||||
for (const value of candidates) {
|
||||
if (typeof value !== "string") {
|
||||
continue;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
const trimmed = normalizeOptionalString(value);
|
||||
if (trimmed) {
|
||||
return trimmed;
|
||||
}
|
||||
@@ -312,8 +310,9 @@ async function resolveGatewayUrl(api: OpenClawPluginApi): Promise<ResolveUrlResu
|
||||
const scheme = resolveScheme(cfg);
|
||||
const port = resolveGatewayPort(cfg);
|
||||
|
||||
if (typeof pluginCfg.publicUrl === "string" && pluginCfg.publicUrl.trim()) {
|
||||
const url = normalizeUrl(pluginCfg.publicUrl, scheme);
|
||||
const configuredPublicUrl = normalizeOptionalString(pluginCfg.publicUrl);
|
||||
if (configuredPublicUrl) {
|
||||
const url = normalizeUrl(configuredPublicUrl, scheme);
|
||||
if (url) {
|
||||
return { url, source: "plugins.entries.device-pair.config.publicUrl" };
|
||||
}
|
||||
@@ -329,8 +328,8 @@ async function resolveGatewayUrl(api: OpenClawPluginApi): Promise<ResolveUrlResu
|
||||
return { url: `wss://${host}`, source: `gateway.tailscale.mode=${tailscaleMode}` };
|
||||
}
|
||||
|
||||
const remoteUrl = cfg.gateway?.remote?.url;
|
||||
if (typeof remoteUrl === "string" && remoteUrl.trim()) {
|
||||
const remoteUrl = normalizeOptionalString(cfg.gateway?.remote?.url);
|
||||
if (remoteUrl) {
|
||||
const url = normalizeUrl(remoteUrl, scheme);
|
||||
if (url) {
|
||||
return { url, source: "gateway.remote.url" };
|
||||
@@ -478,14 +477,19 @@ function canSendQrPngToChannel(channel: string): boolean {
|
||||
|
||||
function resolveQrReplyTarget(ctx: QrCommandContext): string {
|
||||
if (ctx.channel === "discord") {
|
||||
const senderId = ctx.senderId?.trim() ?? "";
|
||||
const senderId = normalizeOptionalString(ctx.senderId) ?? "";
|
||||
if (senderId) {
|
||||
return senderId.startsWith("user:") || senderId.startsWith("channel:")
|
||||
? senderId
|
||||
: `user:${senderId}`;
|
||||
}
|
||||
}
|
||||
return ctx.senderId?.trim() || ctx.from?.trim() || ctx.to?.trim() || "";
|
||||
return (
|
||||
normalizeOptionalString(ctx.senderId) ||
|
||||
normalizeOptionalString(ctx.from) ||
|
||||
normalizeOptionalString(ctx.to) ||
|
||||
""
|
||||
);
|
||||
}
|
||||
|
||||
const PAIR_SETUP_NON_ISSUING_ACTIONS = new Set([
|
||||
@@ -521,7 +525,7 @@ async function sendQrPngToSupportedChannel(params: {
|
||||
qrFilePath: string;
|
||||
}): Promise<boolean> {
|
||||
const mediaLocalRoots = [path.dirname(params.qrFilePath)];
|
||||
const accountId = params.ctx.accountId?.trim() || undefined;
|
||||
const accountId = normalizeOptionalString(params.ctx.accountId) || undefined;
|
||||
const sender = QR_CHANNEL_SENDERS[params.ctx.channel];
|
||||
if (!sender) {
|
||||
return false;
|
||||
@@ -557,7 +561,7 @@ export default definePluginEntry({
|
||||
description: "Generate setup codes and approve device pairing requests.",
|
||||
acceptsArgs: true,
|
||||
handler: async (ctx) => {
|
||||
const args = ctx.args?.trim() ?? "";
|
||||
const args = normalizeOptionalString(ctx.args) ?? "";
|
||||
const tokens = args.split(/\s+/).filter(Boolean);
|
||||
const action = tokens[0]?.toLowerCase() ?? "";
|
||||
const gatewayClientScopes = Array.isArray(ctx.gatewayClientScopes)
|
||||
@@ -579,7 +583,7 @@ export default definePluginEntry({
|
||||
}
|
||||
|
||||
if (action === "notify") {
|
||||
const notifyAction = tokens[1]?.trim().toLowerCase() ?? "status";
|
||||
const notifyAction = normalizeOptionalString(tokens[1])?.toLowerCase() ?? "status";
|
||||
return await handleNotifyCommand({
|
||||
api,
|
||||
ctx,
|
||||
@@ -594,7 +598,7 @@ export default definePluginEntry({
|
||||
const list = await listDevicePairing();
|
||||
const selected = selectPendingApprovalRequest({
|
||||
pending: list.pending,
|
||||
requested: tokens[1]?.trim(),
|
||||
requested: normalizeOptionalString(tokens[1]),
|
||||
});
|
||||
if (selected.reply) {
|
||||
return selected.reply;
|
||||
@@ -744,7 +748,11 @@ export default definePluginEntry({
|
||||
};
|
||||
}
|
||||
const channel = ctx.channel;
|
||||
const target = ctx.senderId?.trim() || ctx.from?.trim() || ctx.to?.trim() || "";
|
||||
const target =
|
||||
normalizeOptionalString(ctx.senderId) ||
|
||||
normalizeOptionalString(ctx.from) ||
|
||||
normalizeOptionalString(ctx.to) ||
|
||||
"";
|
||||
const payload = await issueSetupPayload(urlResult.url);
|
||||
|
||||
if (channel === "telegram" && target) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import type { OpenClawPluginApi } from "./api.js";
|
||||
import { listDevicePairing } from "./api.js";
|
||||
|
||||
@@ -41,7 +42,7 @@ function formatStringList(values?: readonly string[]): string {
|
||||
}
|
||||
|
||||
function formatRoleList(request: PendingPairingRequest): string {
|
||||
const role = request.role?.trim();
|
||||
const role = normalizeOptionalString(request.role);
|
||||
if (role) {
|
||||
return role;
|
||||
}
|
||||
@@ -58,9 +59,9 @@ export function formatPendingRequests(pending: PendingPairingRequest[]): string
|
||||
}
|
||||
const lines: string[] = ["Pending device pairing requests:"];
|
||||
for (const req of pending) {
|
||||
const label = req.displayName?.trim() || req.deviceId;
|
||||
const platform = req.platform?.trim();
|
||||
const ip = req.remoteIp?.trim();
|
||||
const label = normalizeOptionalString(req.displayName) || req.deviceId;
|
||||
const platform = normalizeOptionalString(req.platform);
|
||||
const ip = normalizeOptionalString(req.remoteIp);
|
||||
const parts = [
|
||||
`- ${req.requestId}`,
|
||||
label ? `name=${label}` : null,
|
||||
@@ -92,17 +93,14 @@ function normalizeNotifyState(raw: unknown): NotifyStateFile {
|
||||
continue;
|
||||
}
|
||||
const record = item as Record<string, unknown>;
|
||||
const to = typeof record.to === "string" ? record.to.trim() : "";
|
||||
const to = normalizeOptionalString(record.to) ?? "";
|
||||
if (!to) {
|
||||
continue;
|
||||
}
|
||||
const accountId =
|
||||
typeof record.accountId === "string" && record.accountId.trim()
|
||||
? record.accountId.trim()
|
||||
: undefined;
|
||||
const accountId = normalizeOptionalString(record.accountId) ?? undefined;
|
||||
const messageThreadId =
|
||||
typeof record.messageThreadId === "string"
|
||||
? record.messageThreadId.trim() || undefined
|
||||
? normalizeOptionalString(record.messageThreadId) || undefined
|
||||
: typeof record.messageThreadId === "number" && Number.isFinite(record.messageThreadId)
|
||||
? Math.trunc(record.messageThreadId)
|
||||
: undefined;
|
||||
@@ -122,13 +120,14 @@ function normalizeNotifyState(raw: unknown): NotifyStateFile {
|
||||
|
||||
const notifiedRequestIds: Record<string, number> = {};
|
||||
for (const [requestId, ts] of Object.entries(notifiedRaw)) {
|
||||
if (!requestId.trim()) {
|
||||
const normalizedRequestId = normalizeOptionalString(requestId);
|
||||
if (!normalizedRequestId) {
|
||||
continue;
|
||||
}
|
||||
if (typeof ts !== "number" || !Number.isFinite(ts) || ts <= 0) {
|
||||
continue;
|
||||
}
|
||||
notifiedRequestIds[requestId] = Math.trunc(ts);
|
||||
notifiedRequestIds[normalizedRequestId] = Math.trunc(ts);
|
||||
}
|
||||
|
||||
return { subscribers, notifiedRequestIds };
|
||||
@@ -170,7 +169,11 @@ function resolveNotifyTarget(ctx: {
|
||||
accountId?: string;
|
||||
messageThreadId?: string | number;
|
||||
}): NotifyTarget | null {
|
||||
const to = ctx.senderId?.trim() || ctx.from?.trim() || ctx.to?.trim() || "";
|
||||
const to =
|
||||
normalizeOptionalString(ctx.senderId) ||
|
||||
normalizeOptionalString(ctx.from) ||
|
||||
normalizeOptionalString(ctx.to) ||
|
||||
"";
|
||||
if (!to) {
|
||||
return null;
|
||||
}
|
||||
@@ -206,9 +209,9 @@ function upsertNotifySubscriber(
|
||||
}
|
||||
|
||||
function buildPairingRequestNotificationText(request: PendingPairingRequest): string {
|
||||
const label = request.displayName?.trim() || request.deviceId;
|
||||
const platform = request.platform?.trim();
|
||||
const ip = request.remoteIp?.trim();
|
||||
const label = normalizeOptionalString(request.displayName) || request.deviceId;
|
||||
const platform = normalizeOptionalString(request.platform);
|
||||
const ip = normalizeOptionalString(request.remoteIp);
|
||||
const role = formatRoleList(request);
|
||||
const scopes = formatScopeList(request);
|
||||
const lines = [
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { approveDevicePairing, listDevicePairing } from "./api.js";
|
||||
import { formatPendingRequests } from "./notify.js";
|
||||
|
||||
@@ -43,8 +44,8 @@ export function selectPendingApprovalRequest(params: {
|
||||
}
|
||||
|
||||
function formatApprovedPairingReply(approved: ApprovedPairingEntry): { text: string } {
|
||||
const label = approved.device.displayName?.trim() || approved.device.deviceId;
|
||||
const platform = approved.device.platform?.trim();
|
||||
const label = normalizeOptionalString(approved.device.displayName) || approved.device.deviceId;
|
||||
const platform = normalizeOptionalString(approved.device.platform);
|
||||
const platformLabel = platform ? ` (${platform})` : "";
|
||||
return { text: `✅ Paired ${label}${platformLabel}.` };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user