mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
Slack: add modal private metadata utilities
This commit is contained in:
55
src/slack/modal-metadata.test.ts
Normal file
55
src/slack/modal-metadata.test.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
encodeSlackModalPrivateMetadata,
|
||||
parseSlackModalPrivateMetadata,
|
||||
} from "./modal-metadata.js";
|
||||
|
||||
describe("parseSlackModalPrivateMetadata", () => {
|
||||
it("returns empty object for missing or invalid values", () => {
|
||||
expect(parseSlackModalPrivateMetadata(undefined)).toEqual({});
|
||||
expect(parseSlackModalPrivateMetadata("")).toEqual({});
|
||||
expect(parseSlackModalPrivateMetadata("{bad-json")).toEqual({});
|
||||
});
|
||||
|
||||
it("parses known metadata fields", () => {
|
||||
expect(
|
||||
parseSlackModalPrivateMetadata(
|
||||
JSON.stringify({
|
||||
sessionKey: "agent:main:slack:channel:C1",
|
||||
channelId: "D123",
|
||||
channelType: "im",
|
||||
ignored: "x",
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
sessionKey: "agent:main:slack:channel:C1",
|
||||
channelId: "D123",
|
||||
channelType: "im",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("encodeSlackModalPrivateMetadata", () => {
|
||||
it("encodes only known non-empty fields", () => {
|
||||
expect(
|
||||
JSON.parse(
|
||||
encodeSlackModalPrivateMetadata({
|
||||
sessionKey: "agent:main:slack:channel:C1",
|
||||
channelId: "",
|
||||
channelType: "im",
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
sessionKey: "agent:main:slack:channel:C1",
|
||||
channelType: "im",
|
||||
});
|
||||
});
|
||||
|
||||
it("throws when encoded payload exceeds Slack metadata limit", () => {
|
||||
expect(() =>
|
||||
encodeSlackModalPrivateMetadata({
|
||||
sessionKey: `agent:main:${"x".repeat(4000)}`,
|
||||
}),
|
||||
).toThrow(/cannot exceed 3000 chars/i);
|
||||
});
|
||||
});
|
||||
42
src/slack/modal-metadata.ts
Normal file
42
src/slack/modal-metadata.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
export type SlackModalPrivateMetadata = {
|
||||
sessionKey?: string;
|
||||
channelId?: string;
|
||||
channelType?: string;
|
||||
};
|
||||
|
||||
const SLACK_PRIVATE_METADATA_MAX = 3000;
|
||||
|
||||
function normalizeString(value: unknown) {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
export function parseSlackModalPrivateMetadata(raw: unknown): SlackModalPrivateMetadata {
|
||||
if (typeof raw !== "string" || raw.trim().length === 0) {
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(raw) as Record<string, unknown>;
|
||||
return {
|
||||
sessionKey: normalizeString(parsed.sessionKey),
|
||||
channelId: normalizeString(parsed.channelId),
|
||||
channelType: normalizeString(parsed.channelType),
|
||||
};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export function encodeSlackModalPrivateMetadata(input: SlackModalPrivateMetadata): string {
|
||||
const payload: SlackModalPrivateMetadata = {
|
||||
...(input.sessionKey ? { sessionKey: input.sessionKey } : {}),
|
||||
...(input.channelId ? { channelId: input.channelId } : {}),
|
||||
...(input.channelType ? { channelType: input.channelType } : {}),
|
||||
};
|
||||
const encoded = JSON.stringify(payload);
|
||||
if (encoded.length > SLACK_PRIVATE_METADATA_MAX) {
|
||||
throw new Error(
|
||||
`Slack modal private_metadata cannot exceed ${SLACK_PRIVATE_METADATA_MAX} chars`,
|
||||
);
|
||||
}
|
||||
return encoded;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import type { SlackActionMiddlewareArgs } from "@slack/bolt";
|
||||
import type { Block, KnownBlock } from "@slack/web-api";
|
||||
import type { SlackMonitorContext } from "../context.js";
|
||||
import { enqueueSystemEvent } from "../../../infra/system-events.js";
|
||||
import { parseSlackModalPrivateMetadata } from "../../modal-metadata.js";
|
||||
|
||||
// Prefix for OpenClaw-generated action IDs to scope our handler
|
||||
const OPENCLAW_ACTION_PREFIX = "openclaw:";
|
||||
@@ -47,12 +48,6 @@ type ModalInputSummary = {
|
||||
inputValue?: string;
|
||||
};
|
||||
|
||||
type ModalPrivateMetadata = {
|
||||
sessionKey?: string;
|
||||
channelId?: string;
|
||||
channelType?: string;
|
||||
};
|
||||
|
||||
function readOptionValues(options: unknown): string[] | undefined {
|
||||
if (!Array.isArray(options)) {
|
||||
return undefined;
|
||||
@@ -152,35 +147,11 @@ function summarizeViewState(values: unknown): ModalInputSummary[] {
|
||||
return entries;
|
||||
}
|
||||
|
||||
function parseModalPrivateMetadata(raw: unknown): ModalPrivateMetadata {
|
||||
if (typeof raw !== "string" || raw.trim().length === 0) {
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(raw) as Record<string, unknown>;
|
||||
const sessionKey =
|
||||
typeof parsed.sessionKey === "string" && parsed.sessionKey.trim().length > 0
|
||||
? parsed.sessionKey
|
||||
: undefined;
|
||||
const channelId =
|
||||
typeof parsed.channelId === "string" && parsed.channelId.trim().length > 0
|
||||
? parsed.channelId
|
||||
: undefined;
|
||||
const channelType =
|
||||
typeof parsed.channelType === "string" && parsed.channelType.trim().length > 0
|
||||
? parsed.channelType
|
||||
: undefined;
|
||||
return { sessionKey, channelId, channelType };
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function resolveModalSessionRouting(params: {
|
||||
ctx: SlackMonitorContext;
|
||||
privateMetadata: unknown;
|
||||
}): { sessionKey: string; channelId?: string; channelType?: string } {
|
||||
const metadata = parseModalPrivateMetadata(params.privateMetadata);
|
||||
const metadata = parseSlackModalPrivateMetadata(params.privateMetadata);
|
||||
if (metadata.sessionKey) {
|
||||
return { sessionKey: metadata.sessionKey };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user