diff --git a/extensions/device-pair/index.ts b/extensions/device-pair/index.ts index eccfa8dc8ee..21bbdef8bac 100644 --- a/extensions/device-pair/index.ts +++ b/extensions/device-pair/index.ts @@ -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 = { }; 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): 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 { 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) { diff --git a/extensions/device-pair/notify.ts b/extensions/device-pair/notify.ts index eb23565b04f..5c1bc6e3f92 100644 --- a/extensions/device-pair/notify.ts +++ b/extensions/device-pair/notify.ts @@ -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; - 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 = {}; 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 = [ diff --git a/extensions/device-pair/pair-command-approve.ts b/extensions/device-pair/pair-command-approve.ts index e703a75a032..e68b86e066b 100644 --- a/extensions/device-pair/pair-command-approve.ts +++ b/extensions/device-pair/pair-command-approve.ts @@ -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}.` }; }