mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-14 04:06:03 +00:00
refactor(googlechat): reuse Google auth gaxios runtime
This commit is contained in:
1
extensions/googlechat/npm-shrinkwrap.json
generated
1
extensions/googlechat/npm-shrinkwrap.json
generated
@@ -8,7 +8,6 @@
|
||||
"name": "@openclaw/googlechat",
|
||||
"version": "2026.7.2",
|
||||
"dependencies": {
|
||||
"gaxios": "7.1.5",
|
||||
"google-auth-library": "10.9.0",
|
||||
"zod": "4.4.3"
|
||||
},
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"gaxios": "7.1.5",
|
||||
"google-auth-library": "10.9.0",
|
||||
"zod": "4.4.3"
|
||||
},
|
||||
|
||||
@@ -30,18 +30,8 @@ async function readGoogleChatCertsResponse(response: Response): Promise<Record<s
|
||||
|
||||
// Size-capped to prevent unbounded growth in long-running deployments (#4948)
|
||||
const MAX_AUTH_CACHE_SIZE = 32;
|
||||
type GoogleAuthModule = typeof import("google-auth-library");
|
||||
type GoogleAuthRuntime = {
|
||||
GoogleAuth: GoogleAuthModule["GoogleAuth"];
|
||||
OAuth2Client: GoogleAuthModule["OAuth2Client"];
|
||||
};
|
||||
type GoogleAuthRuntime = Awaited<ReturnType<typeof loadGoogleAuthRuntime>>;
|
||||
type GoogleAuthInstance = InstanceType<GoogleAuthRuntime["GoogleAuth"]>;
|
||||
type GoogleAuthOptions = ConstructorParameters<GoogleAuthRuntime["GoogleAuth"]>[0];
|
||||
type GoogleAuthTransport = NonNullable<GoogleAuthOptions>["clientOptions"] extends {
|
||||
transporter?: infer T;
|
||||
}
|
||||
? T
|
||||
: never;
|
||||
type OAuth2ClientInstance = InstanceType<GoogleAuthRuntime["OAuth2Client"]>;
|
||||
|
||||
const authCache = new Map<string, { key: string; auth: GoogleAuthInstance }>();
|
||||
@@ -54,9 +44,7 @@ async function getVerifyClient(): Promise<OAuth2ClientInstance> {
|
||||
verifyClientPromise = (async () => {
|
||||
try {
|
||||
const { OAuth2Client } = await loadGoogleAuthRuntime();
|
||||
// google-auth-library types its transporter through gaxios' CJS surface,
|
||||
// while the plugin imports the ESM entrypoint directly.
|
||||
const transporter = (await getGoogleAuthTransport()) as unknown as GoogleAuthTransport;
|
||||
const transporter = await getGoogleAuthTransport();
|
||||
return new OAuth2Client({ transporter });
|
||||
} catch (error) {
|
||||
verifyClientPromise = null;
|
||||
@@ -83,12 +71,11 @@ async function getAuthInstance(account: ResolvedGoogleChatAccount): Promise<Goog
|
||||
if (cached && cached.key === key) {
|
||||
return cached.auth;
|
||||
}
|
||||
const [{ GoogleAuth }, rawTransporter, credentials] = await Promise.all([
|
||||
const [{ GoogleAuth }, transporter, credentials] = await Promise.all([
|
||||
loadGoogleAuthRuntime(),
|
||||
getGoogleAuthTransport(),
|
||||
resolveValidatedGoogleChatCredentials(account),
|
||||
]);
|
||||
const transporter = rawTransporter as unknown as GoogleAuthTransport;
|
||||
|
||||
const evictOldest = () => {
|
||||
if (authCache.size > MAX_AUTH_CACHE_SIZE) {
|
||||
|
||||
@@ -36,8 +36,8 @@ vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
|
||||
fetchWithSsrFGuard: mocks.fetchWithSsrFGuard,
|
||||
}));
|
||||
|
||||
vi.mock("gaxios", () => ({
|
||||
Gaxios: mocks.gaxiosCtor,
|
||||
vi.mock("google-auth-library", () => ({
|
||||
gaxios: { Gaxios: mocks.gaxiosCtor },
|
||||
}));
|
||||
|
||||
let testing: typeof import("./google-auth.runtime.js").testing;
|
||||
@@ -69,7 +69,7 @@ afterEach(() => {
|
||||
|
||||
afterAll(() => {
|
||||
vi.doUnmock("openclaw/plugin-sdk/ssrf-runtime");
|
||||
vi.doUnmock("gaxios");
|
||||
vi.doUnmock("google-auth-library");
|
||||
vi.resetModules();
|
||||
});
|
||||
|
||||
|
||||
@@ -10,33 +10,16 @@ import {
|
||||
import { resolveUserPath } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import type { ResolvedGoogleChatAccount } from "./accounts.js";
|
||||
|
||||
type ProxyRule = RegExp | URL | string;
|
||||
type TlsCert = ConnectionOptions["cert"];
|
||||
type TlsKey = ConnectionOptions["key"];
|
||||
type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
||||
type GoogleAuthModule = typeof import("google-auth-library");
|
||||
type GaxiosModule = typeof import("gaxios");
|
||||
type GoogleAuthRuntime = {
|
||||
Gaxios: GaxiosModule["Gaxios"];
|
||||
GoogleAuth: GoogleAuthModule["GoogleAuth"];
|
||||
OAuth2Client: GoogleAuthModule["OAuth2Client"];
|
||||
};
|
||||
type GoogleAuthTransport = InstanceType<GaxiosModule["Gaxios"]>;
|
||||
type GoogleAuthRequestWithUnknownHeaders = RequestInit & {
|
||||
headers?: unknown;
|
||||
};
|
||||
type GoogleAuthResponseWithUnknownHeaders = {
|
||||
headers?: unknown;
|
||||
};
|
||||
type GuardedGoogleAuthRequestInit = RequestInit & {
|
||||
agent?: unknown;
|
||||
cert?: unknown;
|
||||
dispatcher?: unknown;
|
||||
fetchImplementation?: unknown;
|
||||
key?: unknown;
|
||||
noProxy?: unknown;
|
||||
proxy?: unknown;
|
||||
};
|
||||
type GoogleAuthRuntime = typeof import("google-auth-library");
|
||||
type GoogleAuthTransport = InstanceType<GoogleAuthRuntime["gaxios"]["Gaxios"]>;
|
||||
type GoogleAuthTransportOptions = NonNullable<
|
||||
ConstructorParameters<GoogleAuthRuntime["gaxios"]["Gaxios"]>[0]
|
||||
>;
|
||||
type GoogleAuthTransportInit = GoogleAuthTransportOptions & { dispatcher?: unknown };
|
||||
type ProxyRule = NonNullable<GoogleAuthTransportOptions["noProxy"]>[number];
|
||||
type TlsOptions = {
|
||||
cert?: TlsCert;
|
||||
key?: TlsKey;
|
||||
@@ -48,16 +31,11 @@ type ProxyAgentLike = {
|
||||
type TlsAgentLike = {
|
||||
options?: TlsOptions;
|
||||
};
|
||||
type GoogleChatServiceAccountCredentials = Record<string, unknown> & {
|
||||
auth_provider_x509_cert_url?: string;
|
||||
auth_uri?: string;
|
||||
client_email: string;
|
||||
client_x509_cert_url?: string;
|
||||
private_key: string;
|
||||
token_uri?: string;
|
||||
type?: string;
|
||||
universe_domain?: string;
|
||||
};
|
||||
type GoogleChatServiceAccountCredentials = Record<string, unknown> &
|
||||
import("google-auth-library").JWTInput & {
|
||||
client_email: string;
|
||||
private_key: string;
|
||||
};
|
||||
|
||||
const GOOGLE_AUTH_ALLOWED_HOST_SUFFIXES = ["accounts.google.com", "googleapis.com"];
|
||||
const GOOGLE_AUTH_POLICY = buildHostnameAllowlistPolicyFromSuffixAllowlist(
|
||||
@@ -74,7 +52,7 @@ const MAX_GOOGLE_CHAT_SERVICE_ACCOUNT_FILE_BYTES = 64 * 1024;
|
||||
|
||||
let googleAuthRuntimePromise: Promise<GoogleAuthRuntime> | null = null;
|
||||
|
||||
function normalizeGoogleAuthPreparedRequestHeaders<T extends GoogleAuthRequestWithUnknownHeaders>(
|
||||
function normalizeGoogleAuthPreparedRequestHeaders<T extends RequestInit & { headers?: unknown }>(
|
||||
config: T,
|
||||
): T & { headers: Headers } {
|
||||
if (!(config.headers instanceof Headers)) {
|
||||
@@ -83,7 +61,7 @@ function normalizeGoogleAuthPreparedRequestHeaders<T extends GoogleAuthRequestWi
|
||||
return config as T & { headers: Headers };
|
||||
}
|
||||
|
||||
function normalizeGoogleAuthResponseHeaders<T extends GoogleAuthResponseWithUnknownHeaders>(
|
||||
function normalizeGoogleAuthResponseHeaders<T extends { headers?: unknown }>(
|
||||
response: T,
|
||||
): T & { headers: Headers } {
|
||||
if (!(response.headers instanceof Headers)) {
|
||||
@@ -118,7 +96,7 @@ function hasTlsAgentShape(value: unknown): value is TlsAgentLike {
|
||||
return record !== null && asNullableObjectRecord(record.options) !== null;
|
||||
}
|
||||
|
||||
function resolveGoogleAuthAgent(init: GuardedGoogleAuthRequestInit, url: URL): unknown {
|
||||
function resolveGoogleAuthAgent(init: GoogleAuthTransportOptions, url: URL): unknown {
|
||||
return typeof init.agent === "function" ? init.agent(url) : init.agent;
|
||||
}
|
||||
|
||||
@@ -126,10 +104,10 @@ function hasTlsOptions(options: TlsOptions): boolean {
|
||||
return options.cert !== undefined || options.key !== undefined;
|
||||
}
|
||||
|
||||
function resolveGoogleAuthTlsOptions(init: GuardedGoogleAuthRequestInit, url: URL): TlsOptions {
|
||||
function resolveGoogleAuthTlsOptions(init: GoogleAuthTransportOptions, url: URL): TlsOptions {
|
||||
const explicit = {
|
||||
cert: init.cert as TlsCert | undefined,
|
||||
key: init.key as TlsKey | undefined,
|
||||
cert: init.cert,
|
||||
key: init.key,
|
||||
};
|
||||
if (hasTlsOptions(explicit)) {
|
||||
return explicit;
|
||||
@@ -305,7 +283,7 @@ function validateGoogleChatServiceAccountCredentials(
|
||||
assertExactUrlField(credentials, "token_uri", GOOGLE_AUTH_TOKEN_URI);
|
||||
assertUrlPrefixField(credentials, "client_x509_cert_url", GOOGLE_CLIENT_CERTS_URL_PREFIX);
|
||||
|
||||
return credentials as GoogleChatServiceAccountCredentials;
|
||||
return credentials as unknown as GoogleChatServiceAccountCredentials;
|
||||
}
|
||||
|
||||
async function readCredentialsFile(filePath: string): Promise<Record<string, unknown>> {
|
||||
@@ -360,11 +338,11 @@ async function readCredentialsFile(filePath: string): Promise<Record<string, unk
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeGoogleAuthInit(init?: RequestInit): RequestInit | undefined {
|
||||
function sanitizeGoogleAuthInit(init?: GoogleAuthTransportInit): RequestInit | undefined {
|
||||
if (!init) {
|
||||
return undefined;
|
||||
}
|
||||
const nextInit = { ...(init as GuardedGoogleAuthRequestInit) };
|
||||
const nextInit = { ...init };
|
||||
delete nextInit.agent;
|
||||
delete nextInit.cert;
|
||||
delete nextInit.dispatcher;
|
||||
@@ -387,7 +365,7 @@ function resolveGoogleAuthDispatcherPolicy(
|
||||
? new URL(input.url)
|
||||
: new URL(typeof input === "string" ? input : input.toString());
|
||||
const nextInit = sanitizeGoogleAuthInit(init);
|
||||
const googleAuthInit = (init ?? {}) as GuardedGoogleAuthRequestInit;
|
||||
const googleAuthInit = (init ?? {}) as GoogleAuthTransportInit;
|
||||
const tlsOptions = resolveGoogleAuthTlsOptions(googleAuthInit, requestUrl);
|
||||
const proxyBypassed = shouldBypassGoogleAuthProxy(
|
||||
requestUrl,
|
||||
@@ -515,31 +493,17 @@ async function readGoogleAuthResponseBytes(response: Response): Promise<Uint8Arr
|
||||
}
|
||||
|
||||
export async function loadGoogleAuthRuntime(): Promise<GoogleAuthRuntime> {
|
||||
if (!googleAuthRuntimePromise) {
|
||||
googleAuthRuntimePromise = (async () => {
|
||||
try {
|
||||
const [googleAuthModule, gaxiosModule] = await Promise.all([
|
||||
import("google-auth-library"),
|
||||
import("gaxios"),
|
||||
]);
|
||||
return {
|
||||
Gaxios: gaxiosModule.Gaxios,
|
||||
GoogleAuth: googleAuthModule.GoogleAuth,
|
||||
OAuth2Client: googleAuthModule.OAuth2Client,
|
||||
};
|
||||
} catch (error) {
|
||||
googleAuthRuntimePromise = null;
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
}
|
||||
googleAuthRuntimePromise ??= import("google-auth-library").catch((error) => {
|
||||
googleAuthRuntimePromise = null;
|
||||
throw error;
|
||||
});
|
||||
return await googleAuthRuntimePromise;
|
||||
}
|
||||
|
||||
export async function getGoogleAuthTransport(): Promise<GoogleAuthTransport> {
|
||||
const { Gaxios } = await loadGoogleAuthRuntime();
|
||||
const { gaxios } = await loadGoogleAuthRuntime();
|
||||
return installGoogleAuthHeaderCompatibilityInterceptor(
|
||||
new Gaxios({
|
||||
new gaxios.Gaxios({
|
||||
fetchImplementation: createGoogleAuthFetch(),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -44,22 +44,21 @@ vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("gaxios", () => ({
|
||||
Gaxios: class {
|
||||
defaults: unknown;
|
||||
interceptors = {
|
||||
request: { add: vi.fn() },
|
||||
response: { add: vi.fn() },
|
||||
};
|
||||
|
||||
constructor(defaults?: unknown) {
|
||||
this.defaults = defaults;
|
||||
mocks.gaxiosCtor(defaults);
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("google-auth-library", () => ({
|
||||
gaxios: {
|
||||
Gaxios: class {
|
||||
defaults: unknown;
|
||||
interceptors = {
|
||||
request: { add: vi.fn() },
|
||||
response: { add: vi.fn() },
|
||||
};
|
||||
|
||||
constructor(defaults?: unknown) {
|
||||
this.defaults = defaults;
|
||||
mocks.gaxiosCtor(defaults);
|
||||
}
|
||||
},
|
||||
},
|
||||
GoogleAuth: class {
|
||||
constructor(options?: unknown) {
|
||||
mocks.googleAuthCtor(options);
|
||||
@@ -92,7 +91,6 @@ const { testing: authTesting, getGoogleChatAccessToken, verifyGoogleChatRequest
|
||||
|
||||
afterAll(() => {
|
||||
vi.doUnmock("openclaw/plugin-sdk/ssrf-runtime");
|
||||
vi.doUnmock("gaxios");
|
||||
vi.doUnmock("google-auth-library");
|
||||
vi.doUnmock("./auth.js");
|
||||
vi.resetModules();
|
||||
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -872,9 +872,6 @@ importers:
|
||||
|
||||
extensions/googlechat:
|
||||
dependencies:
|
||||
gaxios:
|
||||
specifier: 7.1.5
|
||||
version: 7.1.5
|
||||
google-auth-library:
|
||||
specifier: 10.9.0
|
||||
version: 10.9.0
|
||||
|
||||
@@ -18,7 +18,7 @@ const packageManifestContractTests: PackageManifestContractParams[] = [
|
||||
{ pluginId: "google-meet" },
|
||||
{
|
||||
pluginId: "googlechat",
|
||||
pluginLocalRuntimeDeps: ["gaxios", "google-auth-library"],
|
||||
pluginLocalRuntimeDeps: ["google-auth-library"],
|
||||
minHostVersionBaseline: "2026.3.22",
|
||||
},
|
||||
{ pluginId: "irc", minHostVersionBaseline: "2026.3.22" },
|
||||
|
||||
Reference in New Issue
Block a user