refactor: dedupe matrix trimmed readers

This commit is contained in:
Peter Steinberger
2026-04-08 00:23:22 +01:00
parent df91db906f
commit ae1cc2d6df
12 changed files with 134 additions and 101 deletions

View File

@@ -11,6 +11,7 @@ import {
} from "openclaw/plugin-sdk/account-id";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { hasConfiguredSecretInput } from "openclaw/plugin-sdk/secret-input";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import {
resolveMatrixAccountStringValues,
type MatrixResolvedStringField,
@@ -21,7 +22,7 @@ import { isRecord } from "./record-shared.js";
type MatrixTopologyStringSources = Partial<Record<MatrixResolvedStringField, string>>;
function readConfiguredMatrixString(value: unknown): string {
return typeof value === "string" ? value.trim() : "";
return normalizeOptionalString(value) ?? "";
}
function readConfiguredMatrixSecretSource(value: unknown): string {
@@ -45,8 +46,7 @@ function resolveMatrixChannelStringSources(
}
function readEnvMatrixString(env: NodeJS.ProcessEnv, key: string): string {
const value = env[key];
return typeof value === "string" ? value.trim() : "";
return normalizeOptionalString(env[key]) ?? "";
}
function resolveScopedMatrixEnvStringSources(

View File

@@ -26,7 +26,7 @@ export type ResolvedMatrixAccount = {
};
function clean(value: unknown): string {
return typeof value === "string" ? value.trim() : "";
return normalizeOptionalString(value) ?? "";
}
function resolveMatrixAccountAuthView(params: {

View File

@@ -1,4 +1,5 @@
import { KeyedAsyncQueue } from "openclaw/plugin-sdk/core";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { inspectMatrixDirectRoomEvidence } from "./direct-room.js";
import type { MatrixClient } from "./sdk.js";
import { EventType, type MatrixDirectAccountData } from "./send/types.js";
@@ -60,7 +61,7 @@ async function readMatrixDirectAccountData(client: MatrixClient): Promise<Matrix
}
function normalizeRemoteUserId(remoteUserId: string): string {
const normalized = remoteUserId.trim();
const normalized = normalizeOptionalString(remoteUserId) ?? "";
if (!isMatrixQualifiedUserId(normalized)) {
throw new Error(`Matrix user IDs must be fully qualified (got "${remoteUserId}")`);
}
@@ -75,7 +76,7 @@ function normalizeMappedRoomIds(direct: MatrixDirectAccountData, remoteUserId: s
const seen = new Set<string>();
const normalized: string[] = [];
for (const value of current) {
const roomId = typeof value === "string" ? value.trim() : "";
const roomId = normalizeOptionalString(value) ?? "";
if (!roomId || seen.has(roomId)) {
continue;
}
@@ -257,7 +258,8 @@ export async function inspectMatrixDirectRooms(params: {
remoteUserId: string;
}): Promise<MatrixDirectRoomInspection> {
const remoteUserId = normalizeRemoteUserId(params.remoteUserId);
const selfUserId = (await params.client.getUserId().catch(() => null))?.trim() || null;
const selfUserId =
normalizeOptionalString(await params.client.getUserId().catch(() => null)) ?? null;
const directContent = await readMatrixDirectAccountData(params.client);
const mappedRoomIds = normalizeMappedRoomIds(directContent, remoteUserId);
const mappedRooms = await Promise.all(

View File

@@ -1,3 +1,4 @@
import { normalizeStringifiedOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { getMatrixRuntime } from "../../runtime.js";
import type { MatrixConfig } from "../../types.js";
import type { MatrixClient } from "../sdk.js";
@@ -18,8 +19,8 @@ export function registerMatrixAutoJoin(params: {
};
const autoJoin = accountConfig.autoJoin ?? "off";
const rawAllowlist = (accountConfig.autoJoinAllowlist ?? [])
.map((entry) => String(entry).trim())
.filter(Boolean);
.map((entry) => normalizeStringifiedOptionalString(entry))
.filter((entry): entry is string => Boolean(entry));
const autoJoinAllowlist = new Set(rawAllowlist);
const allowedRoomIds = new Set(rawAllowlist.filter((entry) => entry.startsWith("!")));
const allowedAliases = rawAllowlist.filter((entry) => entry.startsWith("#"));

View File

@@ -1,3 +1,4 @@
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import type { PluginRuntime, RuntimeLogger } from "../../runtime-api.js";
import type { CoreConfig } from "../../types.js";
import type { MatrixAuth } from "../client.js";
@@ -127,12 +128,13 @@ export function registerMatrixMonitorEvents(params: {
directTracker?.invalidateRoom(roomId);
const eventId = event?.event_id ?? "unknown";
const sender = event?.sender ?? "unknown";
const invitee = typeof event?.state_key === "string" ? event.state_key.trim() : "";
const invitee = normalizeOptionalString(event?.state_key) ?? "";
const senderIsInvitee =
typeof event?.sender === "string" && invitee && event.sender.trim() === invitee;
Boolean(invitee) && (normalizeOptionalString(event?.sender) ?? "") === invitee;
const isDirect = (event?.content as { is_direct?: boolean } | undefined)?.is_direct === true;
if (typeof event?.sender === "string" && event.sender.trim() && !senderIsInvitee) {
directTracker?.rememberInvite?.(roomId, event.sender);
const rememberedSender = normalizeOptionalString(event?.sender);
if (rememberedSender && !senderIsInvitee) {
directTracker?.rememberInvite?.(roomId, rememberedSender);
}
logVerboseMessage(
`matrix: invite room=${roomId} sender=${sender} direct=${String(isDirect)} id=${eventId}`,

View File

@@ -1,4 +1,7 @@
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "openclaw/plugin-sdk/text-runtime";
import type { LocationMessageEventContent } from "../sdk.js";
import { formatLocationText, toLocationContext, type NormalizedLocation } from "./runtime-api.js";
import { EventType } from "./types.js";
@@ -72,7 +75,7 @@ export function resolveMatrixLocation(params: {
if (!isLocation) {
return null;
}
const geoUri = typeof content.geo_uri === "string" ? content.geo_uri.trim() : "";
const geoUri = normalizeOptionalString(content.geo_uri) ?? "";
if (!geoUri) {
return null;
}
@@ -80,7 +83,7 @@ export function resolveMatrixLocation(params: {
if (!parsed) {
return null;
}
const caption = typeof content.body === "string" ? content.body.trim() : "";
const caption = normalizeOptionalString(content.body) ?? "";
const location: NormalizedLocation = {
latitude: parsed.latitude,
longitude: parsed.longitude,

View File

@@ -1,3 +1,5 @@
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
const VERIFICATION_EVENT_PREFIX = "m.key.verification.";
const VERIFICATION_REQUEST_MSGTYPE = "m.key.verification.request";
@@ -11,7 +13,7 @@ const VERIFICATION_NOTICE_PREFIXES = [
];
function trimMaybeString(input: unknown): string {
return typeof input === "string" ? input.trim() : "";
return normalizeOptionalString(input) ?? "";
}
export function isMatrixVerificationEventType(type: unknown): boolean {

View File

@@ -7,6 +7,7 @@
* - m.poll.end - Closes a poll
*/
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { normalizePollInput, type PollInput } from "../runtime-api.js";
export const M_POLL_START = "m.poll.start" as const;
@@ -295,7 +296,7 @@ export function buildPollResultsSummary(params: {
if (!isPollResponseType(typeof event.type === "string" ? event.type : "")) {
continue;
}
const senderId = typeof event.sender === "string" ? event.sender.trim() : "";
const senderId = normalizeOptionalString(event.sender) ?? "";
if (!senderId) {
continue;
}
@@ -310,7 +311,7 @@ export function buildPollResultsSummary(params: {
const normalizedAnswers = Array.from(
new Set(
rawAnswers
.map((answerId) => answerId.trim())
.map((answerId) => normalizeOptionalString(answerId) ?? "")
.filter((answerId) => answerIds.has(answerId))
.slice(0, parsed.maxSelections),
),

View File

@@ -1,3 +1,5 @@
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
export const MATRIX_ANNOTATION_RELATION_TYPE = "m.annotation";
export const MATRIX_REACTION_EVENT_TYPE = "m.reaction";
@@ -83,11 +85,11 @@ export function extractMatrixReactionAnnotation(
) {
return undefined;
}
const key = typeof relatesTo.key === "string" ? relatesTo.key.trim() : "";
const key = normalizeOptionalString(relatesTo.key) ?? "";
if (!key) {
return undefined;
}
const eventId = typeof relatesTo.event_id === "string" ? relatesTo.event_id.trim() : "";
const eventId = normalizeOptionalString(relatesTo.event_id) ?? "";
return {
key,
eventId: eventId || undefined,
@@ -107,7 +109,7 @@ export function summarizeMatrixReactionEvents(
if (!key) {
continue;
}
const sender = event.sender?.trim() ?? "";
const sender = normalizeOptionalString(event.sender) ?? "";
const entry = summaries.get(key) ?? { key, count: 0, users: [] };
entry.count += 1;
if (sender && !entry.users.includes(sender)) {
@@ -123,20 +125,20 @@ export function selectOwnMatrixReactionEventIds(
userId: string,
emoji?: string,
): string[] {
const senderId = userId.trim();
const senderId = normalizeOptionalString(userId) ?? "";
if (!senderId) {
return [];
}
const targetEmoji = emoji?.trim();
const targetEmoji = normalizeOptionalString(emoji);
const ids: string[] = [];
for (const event of events) {
if ((event.sender?.trim() ?? "") !== senderId) {
if ((normalizeOptionalString(event.sender) ?? "") !== senderId) {
continue;
}
if (targetEmoji && extractMatrixReactionKey(event.content) !== targetEmoji) {
continue;
}
const eventId = event.event_id?.trim();
const eventId = normalizeOptionalString(event.event_id);
if (eventId) {
ids.push(eventId);
}

View File

@@ -3,6 +3,7 @@ import os from "node:os";
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import {
findMatrixAccountEntry,
requiresExplicitMatrixDefaultAccount,
@@ -40,7 +41,7 @@ export type MatrixLegacyFlatStoreTarget = MatrixMigrationAccountTarget & {
type MatrixLegacyFlatStoreKind = "state" | "encrypted state";
function clean(value: unknown): string {
return typeof value === "string" ? value.trim() : "";
return normalizeOptionalString(value) ?? "";
}
function resolveMatrixAccountConfigEntry(

View File

@@ -4,7 +4,11 @@ import {
type ChannelSetupWizardAdapter,
} from "openclaw/plugin-sdk/setup";
import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
normalizeStringifiedOptionalString,
} from "openclaw/plugin-sdk/text-runtime";
import { requiresExplicitMatrixDefaultAccount } from "./account-selection.js";
import { listMatrixDirectoryGroupsLive } from "./directory-live.js";
import {
@@ -63,12 +67,18 @@ function isMatrixInviteAutoJoinTarget(entry: string): boolean {
}
function normalizeMatrixInviteAutoJoinTargets(entries: string[]): string[] {
return [...new Set(entries.map((entry) => entry.trim()).filter(Boolean))];
return [
...new Set(
entries
.map((entry) => normalizeOptionalString(entry))
.filter((entry): entry is string => Boolean(entry)),
),
];
}
function resolveMatrixOnboardingAccountId(cfg: CoreConfig, accountId?: string): string {
return normalizeAccountId(
accountId?.trim() || resolveDefaultMatrixAccountId(cfg) || DEFAULT_ACCOUNT_ID,
normalizeOptionalString(accountId) || resolveDefaultMatrixAccountId(cfg) || DEFAULT_ACCOUNT_ID,
);
}
@@ -130,7 +140,7 @@ async function promptMatrixAllowFrom(params: {
message: "Matrix allowFrom (full @user:server; display name only if unique)",
placeholder: "@user:server",
initialValue: existingAllowFrom[0] ? String(existingAllowFrom[0]) : undefined,
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
validate: (value) => (normalizeOptionalString(value) ? undefined : "Required"),
});
const parts = splitSetupEntries(String(entry));
const resolvedIds: string[] = [];
@@ -345,7 +355,7 @@ async function configureMatrixAccessPrompts(params: {
const resolvedIds: string[] = [];
const unresolved: string[] = [];
for (const entry of accessConfig.entries) {
const trimmed = entry.trim();
const trimmed = normalizeOptionalString(entry) ?? "";
if (!trimmed) {
continue;
}
@@ -372,7 +382,12 @@ async function configureMatrixAccessPrompts(params: {
unresolved.push(entry);
}
}
roomKeys = [...resolvedIds, ...unresolved.map((entry) => entry.trim()).filter(Boolean)];
roomKeys = [
...resolvedIds,
...unresolved
.map((entry) => normalizeOptionalString(entry))
.filter((entry): entry is string => Boolean(entry)),
];
if (resolvedIds.length > 0 || unresolved.length > 0) {
await params.prompter.note(
[
@@ -453,12 +468,13 @@ async function runMatrixConfigure(params: {
const defaultAccountId = resolveDefaultMatrixAccountId(next);
let accountId = defaultAccountId || DEFAULT_ACCOUNT_ID;
if (params.intent === "add-account") {
const enteredName = String(
await params.prompter.text({
message: "Matrix account name",
validate: (value) => (value?.trim() ? undefined : "Required"),
}),
).trim();
const enteredName =
normalizeStringifiedOptionalString(
await params.prompter.text({
message: "Matrix account name",
validate: (value) => (normalizeOptionalString(value) ? undefined : "Required"),
}),
) ?? "";
accountId = normalizeAccountId(enteredName);
if (enteredName !== accountId) {
await params.prompter.note(`Account id will be "${accountId}".`, "Matrix account");
@@ -468,7 +484,7 @@ async function runMatrixConfigure(params: {
}
next = updateMatrixAccountConfig(next, accountId, { name: enteredName, enabled: true });
} else {
const override = params.accountOverrides?.[channel]?.trim();
const override = normalizeOptionalString(params.accountOverrides?.[channel]);
if (override) {
accountId = normalizeAccountId(override);
} else if (params.shouldPromptAccountIds) {
@@ -517,22 +533,23 @@ async function runMatrixConfigure(params: {
}
}
const homeserver = String(
await params.prompter.text({
message: "Matrix homeserver URL",
initialValue: existing.homeserver ?? envHomeserver,
validate: (value) => {
try {
validateMatrixHomeserverUrl(String(value ?? ""), {
allowPrivateNetwork: true,
});
return undefined;
} catch (error) {
return error instanceof Error ? error.message : "Invalid Matrix homeserver URL";
}
},
}),
).trim();
const homeserver =
normalizeStringifiedOptionalString(
await params.prompter.text({
message: "Matrix homeserver URL",
initialValue: existing.homeserver ?? envHomeserver,
validate: (value) => {
try {
validateMatrixHomeserverUrl(String(value ?? ""), {
allowPrivateNetwork: true,
});
return undefined;
} catch (error) {
return error instanceof Error ? error.message : "Invalid Matrix homeserver URL";
}
},
}),
) ?? "";
const requiresAllowPrivateNetwork = requiresMatrixPrivateNetworkOptIn(homeserver);
const shouldPromptAllowPrivateNetwork =
requiresAllowPrivateNetwork || isPrivateNetworkOptInEnabled(existing);
@@ -575,50 +592,54 @@ async function runMatrixConfigure(params: {
});
if (authMode === "token") {
accessToken = String(
await params.prompter.text({
message: "Matrix access token",
validate: (value) => (value?.trim() ? undefined : "Required"),
}),
).trim();
accessToken =
normalizeStringifiedOptionalString(
await params.prompter.text({
message: "Matrix access token",
validate: (value) => (normalizeOptionalString(value) ? undefined : "Required"),
}),
) ?? "";
password = undefined;
userId = "";
} else {
userId = String(
await params.prompter.text({
message: "Matrix user ID",
initialValue: existing.userId ?? envUserId,
validate: (value) => {
const raw = String(value ?? "").trim();
if (!raw) {
return "Required";
}
if (!raw.startsWith("@")) {
return "Matrix user IDs should start with @";
}
if (!raw.includes(":")) {
return "Matrix user IDs should include a server (:server)";
}
return undefined;
},
}),
).trim();
password = String(
await params.prompter.text({
message: "Matrix password",
validate: (value) => (value?.trim() ? undefined : "Required"),
}),
).trim();
userId =
normalizeStringifiedOptionalString(
await params.prompter.text({
message: "Matrix user ID",
initialValue: existing.userId ?? envUserId,
validate: (value) => {
const raw = normalizeOptionalString(value) ?? "";
if (!raw) {
return "Required";
}
if (!raw.startsWith("@")) {
return "Matrix user IDs should start with @";
}
if (!raw.includes(":")) {
return "Matrix user IDs should include a server (:server)";
}
return undefined;
},
}),
) ?? "";
password =
normalizeStringifiedOptionalString(
await params.prompter.text({
message: "Matrix password",
validate: (value) => (normalizeOptionalString(value) ? undefined : "Required"),
}),
) ?? "";
accessToken = undefined;
}
}
const deviceName = String(
await params.prompter.text({
message: "Matrix device name (optional)",
initialValue: existing.deviceName ?? "OpenClaw Gateway",
}),
).trim();
const deviceName =
normalizeStringifiedOptionalString(
await params.prompter.text({
message: "Matrix device name (optional)",
initialValue: existing.deviceName ?? "OpenClaw Gateway",
}),
) ?? "";
const enableEncryption = await params.prompter.confirm({
message: "Enable end-to-end encryption (E2EE)?",

View File

@@ -1,4 +1,5 @@
import type { GatewayRequestHandlerOptions } from "openclaw/plugin-sdk/core";
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import { formatMatrixErrorMessage } from "./matrix/errors.js";
function sendError(respond: (ok: boolean, payload?: unknown) => void, err: unknown) {
@@ -18,13 +19,12 @@ export async function handleVerifyRecoveryKey({
}: GatewayRequestHandlerOptions): Promise<void> {
try {
const { verifyMatrixRecoveryKey } = await import("./matrix/actions/verification.js");
const key = typeof params?.key === "string" ? params.key : "";
if (!key.trim()) {
const key = normalizeOptionalString(params?.key);
if (!key) {
respond(false, { error: "key required" });
return;
}
const accountId =
typeof params?.accountId === "string" ? params.accountId.trim() || undefined : undefined;
const accountId = normalizeOptionalString(params?.accountId);
const result = await verifyMatrixRecoveryKey(key, { accountId });
respond(result.success, result);
} catch (err) {
@@ -38,8 +38,7 @@ export async function handleVerificationBootstrap({
}: GatewayRequestHandlerOptions): Promise<void> {
try {
const { bootstrapMatrixVerification } = await import("./matrix/actions/verification.js");
const accountId =
typeof params?.accountId === "string" ? params.accountId.trim() || undefined : undefined;
const accountId = normalizeOptionalString(params?.accountId);
const recoveryKey = typeof params?.recoveryKey === "string" ? params.recoveryKey : undefined;
const forceResetCrossSigning = params?.forceResetCrossSigning === true;
const result = await bootstrapMatrixVerification({
@@ -59,8 +58,7 @@ export async function handleVerificationStatus({
}: GatewayRequestHandlerOptions): Promise<void> {
try {
const { getMatrixVerificationStatus } = await import("./matrix/actions/verification.js");
const accountId =
typeof params?.accountId === "string" ? params.accountId.trim() || undefined : undefined;
const accountId = normalizeOptionalString(params?.accountId);
const includeRecoveryKey = params?.includeRecoveryKey === true;
const status = await getMatrixVerificationStatus({ accountId, includeRecoveryKey });
respond(true, status);