mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:10:45 +00:00
refactor: reuse text runtime in google chat
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
} from "openclaw/plugin-sdk/account-resolution";
|
||||
import { safeParseJsonWithSchema, safeParseWithSchema } from "openclaw/plugin-sdk/extension-shared";
|
||||
import { isSecretRef } from "openclaw/plugin-sdk/secret-input";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { z } from "zod";
|
||||
import type { GoogleChatAccountConfig } from "./types.config.js";
|
||||
|
||||
@@ -27,14 +28,6 @@ const ENV_SERVICE_ACCOUNT = "GOOGLE_CHAT_SERVICE_ACCOUNT";
|
||||
const ENV_SERVICE_ACCOUNT_FILE = "GOOGLE_CHAT_SERVICE_ACCOUNT_FILE";
|
||||
const JsonRecordSchema = z.record(z.string(), z.unknown());
|
||||
|
||||
function normalizeOptionalString(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
}
|
||||
|
||||
const {
|
||||
listAccountIds: listGoogleChatAccountIds,
|
||||
resolveDefaultAccountId: resolveDefaultGoogleChatAccountId,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { GoogleAuth, OAuth2Client } from "google-auth-library";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { fetchWithSsrFGuard } from "../runtime-api.js";
|
||||
import type { ResolvedGoogleChatAccount } from "./accounts.js";
|
||||
|
||||
@@ -16,10 +17,6 @@ const verifyClient = new OAuth2Client();
|
||||
|
||||
let cachedCerts: { fetchedAt: number; certs: Record<string, string> } | null = null;
|
||||
|
||||
function normalizeLowercaseStringOrEmpty(value: unknown): string {
|
||||
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
||||
}
|
||||
|
||||
function buildAuthKey(account: ResolvedGoogleChatAccount): string {
|
||||
if (account.credentialsFile) {
|
||||
return `file:${account.credentialsFile}`;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import type { WebhookInFlightLimiter } from "openclaw/plugin-sdk/webhook-request-guards";
|
||||
import { readJsonWebhookBodyOrReject } from "openclaw/plugin-sdk/webhook-request-guards";
|
||||
import {
|
||||
@@ -14,10 +15,6 @@ import type {
|
||||
GoogleChatUser,
|
||||
} from "./types.js";
|
||||
|
||||
function normalizeLowercaseStringOrEmpty(value: unknown): string {
|
||||
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
||||
}
|
||||
|
||||
function extractBearerToken(header: unknown): string {
|
||||
const authHeader = Array.isArray(header)
|
||||
? typeof header[0] === "string"
|
||||
@@ -104,6 +101,19 @@ function parseGoogleChatInboundPayload(
|
||||
return { ok: true, event, addOnBearerToken };
|
||||
}
|
||||
|
||||
async function isAuthorizedGoogleChatTarget(
|
||||
target: WebhookTarget,
|
||||
bearer: string,
|
||||
): Promise<boolean> {
|
||||
const verification = await verifyGoogleChatRequest({
|
||||
bearer,
|
||||
audienceType: target.audienceType,
|
||||
audience: target.audience,
|
||||
expectedAddOnPrincipal: target.account.config.appPrincipal,
|
||||
});
|
||||
return verification.ok;
|
||||
}
|
||||
|
||||
export function createGoogleChatWebhookRequestHandler(params: {
|
||||
webhookTargets: Map<string, WebhookTarget[]>;
|
||||
webhookInFlightLimiter: WebhookInFlightLimiter;
|
||||
@@ -149,15 +159,7 @@ export function createGoogleChatWebhookRequestHandler(params: {
|
||||
selectedTarget = await resolveWebhookTargetWithAuthOrReject({
|
||||
targets,
|
||||
res,
|
||||
isMatch: async (target) => {
|
||||
const verification = await verifyGoogleChatRequest({
|
||||
bearer: headerBearer,
|
||||
audienceType: target.audienceType,
|
||||
audience: target.audience,
|
||||
expectedAddOnPrincipal: target.account.config.appPrincipal,
|
||||
});
|
||||
return verification.ok;
|
||||
},
|
||||
isMatch: (target) => isAuthorizedGoogleChatTarget(target, headerBearer),
|
||||
});
|
||||
if (!selectedTarget) {
|
||||
return true;
|
||||
@@ -184,15 +186,7 @@ export function createGoogleChatWebhookRequestHandler(params: {
|
||||
selectedTarget = await resolveWebhookTargetWithAuthOrReject({
|
||||
targets,
|
||||
res,
|
||||
isMatch: async (target) => {
|
||||
const verification = await verifyGoogleChatRequest({
|
||||
bearer: parsed.addOnBearerToken,
|
||||
audienceType: target.audienceType,
|
||||
audience: target.audience,
|
||||
expectedAddOnPrincipal: target.account.config.appPrincipal,
|
||||
});
|
||||
return verification.ok;
|
||||
},
|
||||
isMatch: (target) => isAuthorizedGoogleChatTarget(target, parsed.addOnBearerToken),
|
||||
});
|
||||
if (!selectedTarget) {
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
function normalizeLowercaseStringOrEmpty(value: unknown): string {
|
||||
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
||||
}
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
function normalizeUserId(raw?: string | null): string {
|
||||
const trimmed = typeof raw === "string" ? raw.trim() : "";
|
||||
|
||||
@@ -11,6 +11,10 @@ import {
|
||||
type ChannelSetupDmPolicy,
|
||||
type ChannelSetupWizard,
|
||||
} from "openclaw/plugin-sdk/setup";
|
||||
import {
|
||||
normalizeOptionalString,
|
||||
normalizeStringifiedOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveDefaultGoogleChatAccountId, resolveGoogleChatAccount } from "./accounts.js";
|
||||
|
||||
const channel = "googlechat" as const;
|
||||
@@ -19,23 +23,8 @@ const ENV_SERVICE_ACCOUNT_FILE = "GOOGLE_CHAT_SERVICE_ACCOUNT_FILE";
|
||||
const USE_ENV_FLAG = "__googlechatUseEnv";
|
||||
const AUTH_METHOD_FLAG = "__googlechatAuthMethod";
|
||||
|
||||
function normalizeOptionalString(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function normalizeStringifiedOptionalString(value: unknown): string | undefined {
|
||||
if (typeof value === "string") {
|
||||
return normalizeOptionalString(value);
|
||||
}
|
||||
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
||||
return normalizeOptionalString(String(value));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
type GoogleChatTextInput = NonNullable<ChannelSetupWizard["textInputs"]>[number];
|
||||
type GoogleChatTextInputKey = GoogleChatTextInput["inputKey"];
|
||||
|
||||
const promptAllowFrom = createPromptParsedAllowFromForAccount({
|
||||
defaultAccountId: resolveDefaultGoogleChatAccountId,
|
||||
@@ -104,6 +93,32 @@ const googlechatDmPolicy: ChannelSetupDmPolicy = {
|
||||
|
||||
export { googlechatSetupAdapter } from "./setup-core.js";
|
||||
|
||||
function createServiceAccountTextInput(params: {
|
||||
inputKey: GoogleChatTextInputKey;
|
||||
message: string;
|
||||
placeholder: string;
|
||||
authMethod: "file" | "inline";
|
||||
patchKey: "serviceAccountFile" | "serviceAccount";
|
||||
}): GoogleChatTextInput {
|
||||
return {
|
||||
inputKey: params.inputKey,
|
||||
message: params.message,
|
||||
placeholder: params.placeholder,
|
||||
shouldPrompt: ({ credentialValues }) =>
|
||||
credentialValues[USE_ENV_FLAG] !== "1" &&
|
||||
credentialValues[AUTH_METHOD_FLAG] === params.authMethod,
|
||||
validate: ({ value }) => (normalizeStringifiedOptionalString(value) ? undefined : "Required"),
|
||||
normalizeValue: ({ value }) => normalizeStringifiedOptionalString(value) ?? "",
|
||||
applySet: async ({ cfg, accountId, value }) =>
|
||||
applySetupAccountConfigPatch({
|
||||
cfg,
|
||||
channelKey: channel,
|
||||
accountId,
|
||||
patch: { [params.patchKey]: value },
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export const googlechatSetupWizard: ChannelSetupWizard = {
|
||||
channel,
|
||||
status: createStandardChannelSetupStatus({
|
||||
@@ -169,38 +184,20 @@ export const googlechatSetupWizard: ChannelSetupWizard = {
|
||||
},
|
||||
credentials: [],
|
||||
textInputs: [
|
||||
{
|
||||
createServiceAccountTextInput({
|
||||
inputKey: "tokenFile",
|
||||
message: "Service account JSON path",
|
||||
placeholder: "/path/to/service-account.json",
|
||||
shouldPrompt: ({ credentialValues }) =>
|
||||
credentialValues[USE_ENV_FLAG] !== "1" && credentialValues[AUTH_METHOD_FLAG] === "file",
|
||||
validate: ({ value }) => (normalizeStringifiedOptionalString(value) ? undefined : "Required"),
|
||||
normalizeValue: ({ value }) => normalizeStringifiedOptionalString(value) ?? "",
|
||||
applySet: async ({ cfg, accountId, value }) =>
|
||||
applySetupAccountConfigPatch({
|
||||
cfg,
|
||||
channelKey: channel,
|
||||
accountId,
|
||||
patch: { serviceAccountFile: value },
|
||||
}),
|
||||
},
|
||||
{
|
||||
authMethod: "file",
|
||||
patchKey: "serviceAccountFile",
|
||||
}),
|
||||
createServiceAccountTextInput({
|
||||
inputKey: "token",
|
||||
message: "Service account JSON (single line)",
|
||||
placeholder: '{"type":"service_account", ... }',
|
||||
shouldPrompt: ({ credentialValues }) =>
|
||||
credentialValues[USE_ENV_FLAG] !== "1" && credentialValues[AUTH_METHOD_FLAG] === "inline",
|
||||
validate: ({ value }) => (normalizeStringifiedOptionalString(value) ? undefined : "Required"),
|
||||
normalizeValue: ({ value }) => normalizeStringifiedOptionalString(value) ?? "",
|
||||
applySet: async ({ cfg, accountId, value }) =>
|
||||
applySetupAccountConfigPatch({
|
||||
cfg,
|
||||
channelKey: channel,
|
||||
accountId,
|
||||
patch: { serviceAccount: value },
|
||||
}),
|
||||
},
|
||||
authMethod: "inline",
|
||||
patchKey: "serviceAccount",
|
||||
}),
|
||||
],
|
||||
finalize: async ({ cfg, accountId, prompter }) => {
|
||||
const account = resolveGoogleChatAccount({
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import type { ResolvedGoogleChatAccount } from "./accounts.js";
|
||||
import { findGoogleChatDirectMessage } from "./api.js";
|
||||
|
||||
function normalizeLowercaseStringOrEmpty(value: unknown): string {
|
||||
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
||||
}
|
||||
|
||||
export function normalizeGoogleChatTarget(raw?: string | null): string | undefined {
|
||||
const trimmed = raw?.trim();
|
||||
if (!trimmed) {
|
||||
|
||||
Reference in New Issue
Block a user