mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 16:20:43 +00:00
Add Google Meet space access controls
This commit is contained in:
committed by
Peter Steinberger
parent
53c4217110
commit
f2c1a56bbd
@@ -108,7 +108,7 @@ describe("google-meet create flow", () => {
|
||||
googleMeetPluginTesting.setCallGatewayFromCliForTests();
|
||||
});
|
||||
|
||||
it("CLI create prints the new meeting URL", async () => {
|
||||
it("CLI create can configure API-created space access", async () => {
|
||||
const fetchMock = vi.fn(async (input: RequestInfo | URL, _init?: RequestInit) => {
|
||||
const url = input instanceof Request ? input.url : input.toString();
|
||||
if (url.includes("oauth2.googleapis.com")) {
|
||||
@@ -142,9 +142,27 @@ describe("google-meet create flow", () => {
|
||||
});
|
||||
|
||||
try {
|
||||
await program.parseAsync(["googlemeet", "create", "--no-join"], { from: "user" });
|
||||
await program.parseAsync(
|
||||
[
|
||||
"googlemeet",
|
||||
"create",
|
||||
"--no-join",
|
||||
"--access-type",
|
||||
"OPEN",
|
||||
"--entry-point-access",
|
||||
"ALL",
|
||||
],
|
||||
{ from: "user" },
|
||||
);
|
||||
expect(stdout.output()).toContain("meeting uri: https://meet.google.com/new-abcd-xyz");
|
||||
expect(stdout.output()).toContain("space: spaces/new-space");
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
"https://meet.googleapis.com/v2/spaces",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
body: JSON.stringify({ config: { accessType: "OPEN", entryPointAccess: "ALL" } }),
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
stdout.restore();
|
||||
}
|
||||
@@ -220,6 +238,27 @@ describe("google-meet create flow", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects access policy flags when tool create would use browser fallback", async () => {
|
||||
const { methods } = setup(
|
||||
{
|
||||
defaultTransport: "chrome-node",
|
||||
chromeNode: { node: "parallels-macos" },
|
||||
},
|
||||
{
|
||||
nodesInvokeHandler: async () => {
|
||||
throw new Error("browser fallback should not run");
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await expect(
|
||||
invokeGoogleMeetGatewayMethodForTest(methods, "googlemeet.create", {
|
||||
join: false,
|
||||
accessType: "OPEN",
|
||||
}),
|
||||
).rejects.toThrow("access policy options require OAuth/API room creation");
|
||||
});
|
||||
|
||||
it("reports structured manual action when browser creation needs Google login", async () => {
|
||||
const { methods } = setup(
|
||||
{
|
||||
|
||||
@@ -557,6 +557,7 @@ describe("google-meet plugin", () => {
|
||||
"export",
|
||||
"recover_current_tab",
|
||||
"leave",
|
||||
"end_active_conference",
|
||||
"speak",
|
||||
"test_speech",
|
||||
],
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from "./src/config.js";
|
||||
import {
|
||||
buildGoogleMeetPreflightReport,
|
||||
endGoogleMeetActiveConference,
|
||||
fetchGoogleMeetArtifacts,
|
||||
fetchGoogleMeetAttendance,
|
||||
fetchLatestGoogleMeetConferenceRecord,
|
||||
@@ -201,6 +202,7 @@ const GoogleMeetToolSchema = Type.Object({
|
||||
"export",
|
||||
"recover_current_tab",
|
||||
"leave",
|
||||
"end_active_conference",
|
||||
"speak",
|
||||
"test_speech",
|
||||
],
|
||||
@@ -212,6 +214,19 @@ const GoogleMeetToolSchema = Type.Object({
|
||||
description: "For action=create, set false to create the URL without joining.",
|
||||
}),
|
||||
),
|
||||
accessType: Type.Optional(
|
||||
Type.String({
|
||||
enum: ["OPEN", "TRUSTED", "RESTRICTED"],
|
||||
description:
|
||||
"For action=create with Google Meet OAuth, configure who can join without knocking.",
|
||||
}),
|
||||
),
|
||||
entryPointAccess: Type.Optional(
|
||||
Type.String({
|
||||
enum: ["ALL", "CREATOR_APP_ONLY"],
|
||||
description: "For action=create with Google Meet OAuth, configure allowed join entry points.",
|
||||
}),
|
||||
),
|
||||
url: Type.Optional(Type.String({ description: "Explicit https://meet.google.com/... URL" })),
|
||||
transport: Type.Optional(
|
||||
Type.String({ enum: ["chrome", "chrome-node", "twilio"], description: "Join transport" }),
|
||||
@@ -343,6 +358,7 @@ type GoogleMeetGatewayToolAction =
|
||||
| "recover_current_tab"
|
||||
| "setup_status"
|
||||
| "leave"
|
||||
| "end_active_conference"
|
||||
| "speak"
|
||||
| "test_speech";
|
||||
|
||||
@@ -354,6 +370,8 @@ function googleMeetGatewayMethodForToolAction(action: GoogleMeetGatewayToolActio
|
||||
return "googlemeet.setup";
|
||||
case "test_speech":
|
||||
return "googlemeet.testSpeech";
|
||||
case "end_active_conference":
|
||||
return "googlemeet.endActiveConference";
|
||||
default:
|
||||
return `googlemeet.${action}`;
|
||||
}
|
||||
@@ -842,6 +860,25 @@ export default definePluginEntry({
|
||||
},
|
||||
);
|
||||
|
||||
api.registerGatewayMethod(
|
||||
"googlemeet.endActiveConference",
|
||||
async ({ params, respond }: GatewayRequestHandlerOptions) => {
|
||||
try {
|
||||
const raw = asParamRecord(params);
|
||||
const token = await resolveGoogleMeetTokenFromParams(config, raw);
|
||||
respond(
|
||||
true,
|
||||
await endGoogleMeetActiveConference({
|
||||
accessToken: token.accessToken,
|
||||
meeting: resolveMeetingInput(config, raw.meeting),
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
sendError(respond, err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.registerGatewayMethod(
|
||||
"googlemeet.speak",
|
||||
async ({ params, respond }: GatewayRequestHandlerOptions) => {
|
||||
@@ -999,6 +1036,15 @@ export default definePluginEntry({
|
||||
}
|
||||
return json(await callGoogleMeetGatewayFromTool({ config, action: "leave", raw }));
|
||||
}
|
||||
case "end_active_conference": {
|
||||
return json(
|
||||
await callGoogleMeetGatewayFromTool({
|
||||
config,
|
||||
action: "end_active_conference",
|
||||
raw,
|
||||
}),
|
||||
);
|
||||
}
|
||||
case "speak": {
|
||||
const sessionId = normalizeOptionalString(raw.sessionId);
|
||||
if (!sessionId) {
|
||||
|
||||
@@ -324,6 +324,64 @@ describe("google-meet CLI", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("ends an active conference for a Meet space", async () => {
|
||||
const fetchMock = vi.fn(async (input: RequestInfo | URL, _init?: RequestInit) => {
|
||||
const url = requestUrl(input);
|
||||
if (url.pathname === "/v2/spaces/abc-defg-hij") {
|
||||
return jsonResponse({
|
||||
name: "spaces/space-resource-123",
|
||||
meetingCode: "abc-defg-hij",
|
||||
meetingUri: "https://meet.google.com/abc-defg-hij",
|
||||
});
|
||||
}
|
||||
if (url.pathname === "/v2/spaces/space-resource-123:endActiveConference") {
|
||||
return jsonResponse({});
|
||||
}
|
||||
return new Response("not found", { status: 404 });
|
||||
});
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
const stdout = captureStdout();
|
||||
try {
|
||||
await setupCli({}).parseAsync(
|
||||
[
|
||||
"googlemeet",
|
||||
"end-active-conference",
|
||||
"https://meet.google.com/abc-defg-hij",
|
||||
"--access-token",
|
||||
"token",
|
||||
"--expires-at",
|
||||
String(Date.now() + 120_000),
|
||||
"--json",
|
||||
],
|
||||
{ from: "user" },
|
||||
);
|
||||
expect(JSON.parse(stdout.output())).toMatchObject({
|
||||
space: "spaces/space-resource-123",
|
||||
ended: true,
|
||||
tokenSource: "cached-access-token",
|
||||
});
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
"https://meet.googleapis.com/v2/spaces/space-resource-123:endActiveConference",
|
||||
expect.objectContaining({ method: "POST", body: "{}" }),
|
||||
);
|
||||
} finally {
|
||||
stdout.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects access policy flags when create would use browser fallback", async () => {
|
||||
await expect(
|
||||
setupCli({
|
||||
runtime: {
|
||||
createViaBrowser: vi.fn(async () => {
|
||||
throw new Error("browser fallback should not run");
|
||||
}),
|
||||
},
|
||||
}).parseAsync(["googlemeet", "create", "--access-type", "OPEN"], { from: "user" }),
|
||||
).rejects.toThrow("access policy options require OAuth/API room creation");
|
||||
});
|
||||
|
||||
it("prints the latest conference record", async () => {
|
||||
stubMeetArtifactsApi();
|
||||
const stdout = captureStdout();
|
||||
|
||||
@@ -10,9 +10,11 @@ import {
|
||||
type GoogleMeetCalendarLookupResult,
|
||||
} from "./calendar.js";
|
||||
import type { GoogleMeetConfig, GoogleMeetMode, GoogleMeetTransport } from "./config.js";
|
||||
import { hasCreateSpaceConfigInput, resolveCreateSpaceConfig } from "./create.js";
|
||||
import {
|
||||
buildGoogleMeetPreflightReport,
|
||||
createGoogleMeetSpace,
|
||||
endGoogleMeetActiveConference,
|
||||
fetchGoogleMeetArtifacts,
|
||||
fetchGoogleMeetAttendance,
|
||||
fetchLatestGoogleMeetConferenceRecord,
|
||||
@@ -159,6 +161,8 @@ type CreateOptions = {
|
||||
clientId?: string;
|
||||
clientSecret?: string;
|
||||
expiresAt?: string;
|
||||
accessType?: string;
|
||||
entryPointAccess?: string;
|
||||
join?: boolean;
|
||||
transport?: GoogleMeetTransport;
|
||||
mode?: GoogleMeetMode;
|
||||
@@ -1367,6 +1371,14 @@ export function registerGoogleMeetCli(params: {
|
||||
.option("--client-id <id>", "OAuth client id override")
|
||||
.option("--client-secret <secret>", "OAuth client secret override")
|
||||
.option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds")
|
||||
.option(
|
||||
"--access-type <type>",
|
||||
"Google Meet SpaceConfig accessType for API create: OPEN, TRUSTED, or RESTRICTED",
|
||||
)
|
||||
.option(
|
||||
"--entry-point-access <type>",
|
||||
"Google Meet SpaceConfig entryPointAccess for API create: ALL or CREATOR_APP_ONLY",
|
||||
)
|
||||
.option("--no-join", "Only create the meeting URL; do not join it")
|
||||
.option("--transport <transport>", "Join transport: chrome, chrome-node, or twilio")
|
||||
.option(
|
||||
@@ -1380,6 +1392,11 @@ export function registerGoogleMeetCli(params: {
|
||||
.option("--json", "Print JSON output", false)
|
||||
.action(async (options: CreateOptions) => {
|
||||
if (!hasCreateOAuth(params.config, options)) {
|
||||
if (hasCreateSpaceConfigInput(options as Record<string, unknown>)) {
|
||||
throw new Error(
|
||||
"Google Meet access policy options require OAuth/API room creation. Configure Google Meet OAuth or remove --access-type/--entry-point-access.",
|
||||
);
|
||||
}
|
||||
const rt = await params.ensureRuntime();
|
||||
const result = await rt.createViaBrowser();
|
||||
const join =
|
||||
@@ -1423,7 +1440,10 @@ export function registerGoogleMeetCli(params: {
|
||||
const token = await resolveGoogleMeetAccessToken(
|
||||
resolveCreateTokenOptions(params.config, options),
|
||||
);
|
||||
const result = await createGoogleMeetSpace({ accessToken: token.accessToken });
|
||||
const result = await createGoogleMeetSpace({
|
||||
accessToken: token.accessToken,
|
||||
config: resolveCreateSpaceConfig(options as Record<string, unknown>),
|
||||
});
|
||||
const join =
|
||||
options.join !== false
|
||||
? await (
|
||||
@@ -1463,6 +1483,39 @@ export function registerGoogleMeetCli(params: {
|
||||
}
|
||||
});
|
||||
|
||||
root
|
||||
.command("end-active-conference")
|
||||
.description("End the active conference for a Google Meet space")
|
||||
.argument("[meeting]", "Meet URL, meeting code, or spaces/{id}")
|
||||
.option("--access-token <token>", "Access token override")
|
||||
.option("--refresh-token <token>", "Refresh token override")
|
||||
.option("--client-id <id>", "OAuth client id override")
|
||||
.option("--client-secret <secret>", "OAuth client secret override")
|
||||
.option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds")
|
||||
.option("--json", "Print JSON output", false)
|
||||
.action(async (meeting: string | undefined, options: ResolveSpaceOptions & JsonOptions) => {
|
||||
const token = await resolveGoogleMeetAccessToken(
|
||||
resolveOAuthTokenOptions(params.config, options),
|
||||
);
|
||||
const result = await endGoogleMeetActiveConference({
|
||||
accessToken: token.accessToken,
|
||||
meeting: resolveMeetingInput(params.config, meeting ?? options.meeting),
|
||||
});
|
||||
if (options.json) {
|
||||
writeStdoutJson({
|
||||
...result,
|
||||
tokenSource: token.refreshed ? "refresh-token" : "cached-access-token",
|
||||
});
|
||||
return;
|
||||
}
|
||||
writeStdoutLine("space: %s", result.space);
|
||||
writeStdoutLine("ended: yes");
|
||||
writeStdoutLine(
|
||||
"token source: %s",
|
||||
token.refreshed ? "refresh-token" : "cached-access-token",
|
||||
);
|
||||
});
|
||||
|
||||
root
|
||||
.command("join")
|
||||
.argument("[url]", "Explicit https://meet.google.com/... URL")
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import type { GoogleMeetConfig, GoogleMeetMode, GoogleMeetTransport } from "./config.js";
|
||||
import { createGoogleMeetSpace } from "./meet.js";
|
||||
import {
|
||||
createGoogleMeetSpace,
|
||||
type GoogleMeetAccessType,
|
||||
type GoogleMeetEntryPointAccess,
|
||||
type GoogleMeetSpaceConfig,
|
||||
} from "./meet.js";
|
||||
import { resolveGoogleMeetAccessToken } from "./oauth.js";
|
||||
import type { GoogleMeetRuntime } from "./runtime.js";
|
||||
import { createMeetWithBrowserProxyOnNode } from "./transports/chrome-create.js";
|
||||
@@ -14,6 +19,47 @@ function normalizeMode(value: unknown): GoogleMeetMode | undefined {
|
||||
return value === "realtime" || value === "transcribe" ? value : undefined;
|
||||
}
|
||||
|
||||
export function normalizeGoogleMeetAccessType(value: unknown): GoogleMeetAccessType | undefined {
|
||||
const normalized = normalizeOptionalString(value)?.toUpperCase().replaceAll("-", "_");
|
||||
return normalized === "OPEN" || normalized === "TRUSTED" || normalized === "RESTRICTED"
|
||||
? normalized
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function normalizeGoogleMeetEntryPointAccess(
|
||||
value: unknown,
|
||||
): GoogleMeetEntryPointAccess | undefined {
|
||||
const normalized = normalizeOptionalString(value)?.toUpperCase().replaceAll("-", "_");
|
||||
return normalized === "ALL" || normalized === "CREATOR_APP_ONLY" ? normalized : undefined;
|
||||
}
|
||||
|
||||
export function resolveCreateSpaceConfig(
|
||||
raw: Record<string, unknown>,
|
||||
): GoogleMeetSpaceConfig | undefined {
|
||||
const rawAccessType = normalizeOptionalString(raw.accessType);
|
||||
const rawEntryPointAccess = normalizeOptionalString(raw.entryPointAccess);
|
||||
const accessType = normalizeGoogleMeetAccessType(raw.accessType);
|
||||
const entryPointAccess = normalizeGoogleMeetEntryPointAccess(raw.entryPointAccess);
|
||||
if (rawAccessType !== undefined && !accessType) {
|
||||
throw new Error("Invalid Google Meet accessType. Expected OPEN, TRUSTED, or RESTRICTED.");
|
||||
}
|
||||
if (rawEntryPointAccess !== undefined && !entryPointAccess) {
|
||||
throw new Error("Invalid Google Meet entryPointAccess. Expected ALL or CREATOR_APP_ONLY.");
|
||||
}
|
||||
const config = {
|
||||
...(accessType ? { accessType } : {}),
|
||||
...(entryPointAccess ? { entryPointAccess } : {}),
|
||||
};
|
||||
return Object.keys(config).length > 0 ? config : undefined;
|
||||
}
|
||||
|
||||
export function hasCreateSpaceConfigInput(raw: Record<string, unknown>): boolean {
|
||||
return (
|
||||
normalizeOptionalString(raw.accessType) !== undefined ||
|
||||
normalizeOptionalString(raw.entryPointAccess) !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
async function createSpaceFromParams(config: GoogleMeetConfig, raw: Record<string, unknown>) {
|
||||
const token = await resolveGoogleMeetAccessToken({
|
||||
clientId: normalizeOptionalString(raw.clientId) ?? config.oauth.clientId,
|
||||
@@ -22,7 +68,10 @@ async function createSpaceFromParams(config: GoogleMeetConfig, raw: Record<strin
|
||||
accessToken: normalizeOptionalString(raw.accessToken) ?? config.oauth.accessToken,
|
||||
expiresAt: typeof raw.expiresAt === "number" ? raw.expiresAt : config.oauth.expiresAt,
|
||||
});
|
||||
const result = await createGoogleMeetSpace({ accessToken: token.accessToken });
|
||||
const result = await createGoogleMeetSpace({
|
||||
accessToken: token.accessToken,
|
||||
config: resolveCreateSpaceConfig(raw),
|
||||
});
|
||||
return { source: "api" as const, token, ...result };
|
||||
}
|
||||
|
||||
@@ -53,6 +102,11 @@ export async function createMeetFromParams(params: {
|
||||
"URL-only creation was requested. Call google_meet with action=join and url=meetingUri to enter the meeting.",
|
||||
};
|
||||
}
|
||||
if (hasCreateSpaceConfigInput(params.raw)) {
|
||||
throw new Error(
|
||||
"Google Meet access policy options require OAuth/API room creation. Configure Google Meet OAuth or remove accessType/entryPointAccess.",
|
||||
);
|
||||
}
|
||||
const browser = await createMeetWithBrowserProxyOnNode({
|
||||
runtime: params.runtime,
|
||||
config: params.config,
|
||||
|
||||
@@ -9,16 +9,26 @@ const GOOGLE_MEET_API_HOST = "meet.googleapis.com";
|
||||
const GOOGLE_MEET_MEDIA_SCOPE =
|
||||
"https://www.googleapis.com/auth/meetings.conference.media.readonly";
|
||||
const GOOGLE_MEET_SPACE_SCOPE = "https://www.googleapis.com/auth/meetings.space.readonly";
|
||||
const GOOGLE_MEET_SPACE_CREATED_SCOPE = "https://www.googleapis.com/auth/meetings.space.created";
|
||||
const GOOGLE_MEET_SPACE_SETTINGS_SCOPE = "https://www.googleapis.com/auth/meetings.space.settings";
|
||||
|
||||
type GoogleMeetSpace = {
|
||||
export type GoogleMeetAccessType = "OPEN" | "TRUSTED" | "RESTRICTED";
|
||||
export type GoogleMeetEntryPointAccess = "ALL" | "CREATOR_APP_ONLY";
|
||||
|
||||
export type GoogleMeetSpaceConfig = {
|
||||
accessType?: GoogleMeetAccessType;
|
||||
entryPointAccess?: GoogleMeetEntryPointAccess;
|
||||
};
|
||||
|
||||
export type GoogleMeetSpace = {
|
||||
name: string;
|
||||
meetingCode?: string;
|
||||
meetingUri?: string;
|
||||
activeConference?: Record<string, unknown>;
|
||||
config?: Record<string, unknown>;
|
||||
config?: GoogleMeetSpaceConfig & Record<string, unknown>;
|
||||
};
|
||||
|
||||
type GoogleMeetPreflightReport = {
|
||||
export type GoogleMeetPreflightReport = {
|
||||
input: string;
|
||||
resolvedSpaceName: string;
|
||||
meetingCode?: string;
|
||||
@@ -29,12 +39,17 @@ type GoogleMeetPreflightReport = {
|
||||
blockers: string[];
|
||||
};
|
||||
|
||||
type GoogleMeetCreateSpaceResult = {
|
||||
export type GoogleMeetCreateSpaceResult = {
|
||||
space: GoogleMeetSpace;
|
||||
meetingUri: string;
|
||||
};
|
||||
|
||||
type GoogleMeetConferenceRecord = {
|
||||
export type GoogleMeetEndActiveConferenceResult = {
|
||||
space: string;
|
||||
ended: true;
|
||||
};
|
||||
|
||||
export type GoogleMeetConferenceRecord = {
|
||||
name: string;
|
||||
space?: string;
|
||||
startTime?: string;
|
||||
@@ -353,7 +368,12 @@ export async function fetchGoogleMeetSpace(params: {
|
||||
|
||||
export async function createGoogleMeetSpace(params: {
|
||||
accessToken: string;
|
||||
config?: GoogleMeetSpaceConfig;
|
||||
}): Promise<GoogleMeetCreateSpaceResult> {
|
||||
const body =
|
||||
params.config && Object.keys(params.config).length > 0
|
||||
? JSON.stringify({ config: params.config })
|
||||
: "{}";
|
||||
const { response, release } = await fetchWithSsrFGuard({
|
||||
url: `${GOOGLE_MEET_API_BASE_URL}/spaces`,
|
||||
init: {
|
||||
@@ -363,7 +383,7 @@ export async function createGoogleMeetSpace(params: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: "{}",
|
||||
body,
|
||||
},
|
||||
policy: { allowedHostnames: [GOOGLE_MEET_API_HOST] },
|
||||
auditContext: "google-meet.spaces.create",
|
||||
@@ -375,7 +395,10 @@ export async function createGoogleMeetSpace(params: {
|
||||
response,
|
||||
detail,
|
||||
prefix: "Google Meet spaces.create",
|
||||
scopes: ["https://www.googleapis.com/auth/meetings.space.created"],
|
||||
scopes:
|
||||
params.config && Object.keys(params.config).length > 0
|
||||
? [GOOGLE_MEET_SPACE_CREATED_SCOPE, GOOGLE_MEET_SPACE_SETTINGS_SCOPE]
|
||||
: [GOOGLE_MEET_SPACE_CREATED_SCOPE],
|
||||
});
|
||||
}
|
||||
const payload = (await response.json()) as GoogleMeetSpace;
|
||||
@@ -392,7 +415,46 @@ export async function createGoogleMeetSpace(params: {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchGoogleMeetConferenceRecord(params: {
|
||||
export async function endGoogleMeetActiveConference(params: {
|
||||
accessToken: string;
|
||||
meeting: string;
|
||||
}): Promise<GoogleMeetEndActiveConferenceResult> {
|
||||
const resolved = await fetchGoogleMeetSpace({
|
||||
accessToken: params.accessToken,
|
||||
meeting: params.meeting,
|
||||
});
|
||||
const space = resolved.name;
|
||||
const { response, release } = await fetchWithSsrFGuard({
|
||||
url: `${GOOGLE_MEET_API_BASE_URL}/${encodeSpaceNameForPath(space)}:endActiveConference`,
|
||||
init: {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: "{}",
|
||||
},
|
||||
policy: { allowedHostnames: [GOOGLE_MEET_API_HOST] },
|
||||
auditContext: "google-meet.spaces.endActiveConference",
|
||||
});
|
||||
try {
|
||||
if (!response.ok) {
|
||||
const detail = await response.text();
|
||||
throw await googleApiError({
|
||||
response,
|
||||
detail,
|
||||
prefix: "Google Meet spaces.endActiveConference",
|
||||
scopes: [GOOGLE_MEET_SPACE_CREATED_SCOPE],
|
||||
});
|
||||
}
|
||||
return { space, ended: true };
|
||||
} finally {
|
||||
await release();
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchGoogleMeetConferenceRecord(params: {
|
||||
accessToken: string;
|
||||
conferenceRecord: string;
|
||||
}): Promise<GoogleMeetConferenceRecord> {
|
||||
|
||||
@@ -13,6 +13,7 @@ const GOOGLE_MEET_TOKEN_HOST = "oauth2.googleapis.com";
|
||||
export const GOOGLE_MEET_SCOPES = [
|
||||
"https://www.googleapis.com/auth/meetings.space.created",
|
||||
"https://www.googleapis.com/auth/meetings.space.readonly",
|
||||
"https://www.googleapis.com/auth/meetings.space.settings",
|
||||
"https://www.googleapis.com/auth/meetings.conference.media.readonly",
|
||||
"https://www.googleapis.com/auth/calendar.events.readonly",
|
||||
"https://www.googleapis.com/auth/drive.meet.readonly",
|
||||
|
||||
Reference in New Issue
Block a user