@openclaw/google-meet 2026.5.1-beta.2 → 2026.5.2-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.create.test.ts +43 -2
- package/index.test.ts +220 -1
- package/index.ts +129 -4
- package/openclaw.plugin.json +3 -0
- package/package.json +4 -4
- package/src/cli.test.ts +107 -0
- package/src/cli.ts +83 -2
- package/src/create.ts +56 -6
- package/src/meet.ts +69 -7
- package/src/oauth.ts +6 -5
- package/src/runtime.ts +133 -3
- package/src/setup.ts +17 -0
- package/src/test-support/plugin-harness.ts +11 -1
- package/src/transports/types.ts +1 -0
- package/src/voice-call-gateway.test.ts +24 -4
- package/src/voice-call-gateway.ts +64 -6
- package/dist/.boundary-tsc.stamp +0 -1
- package/dist/.boundary-tsc.tsbuildinfo +0 -1
package/src/cli.test.ts
CHANGED
|
@@ -324,6 +324,64 @@ describe("google-meet CLI", () => {
|
|
|
324
324
|
}
|
|
325
325
|
});
|
|
326
326
|
|
|
327
|
+
it("ends an active conference for a Meet space", async () => {
|
|
328
|
+
const fetchMock = vi.fn(async (input: RequestInfo | URL, _init?: RequestInit) => {
|
|
329
|
+
const url = requestUrl(input);
|
|
330
|
+
if (url.pathname === "/v2/spaces/abc-defg-hij") {
|
|
331
|
+
return jsonResponse({
|
|
332
|
+
name: "spaces/space-resource-123",
|
|
333
|
+
meetingCode: "abc-defg-hij",
|
|
334
|
+
meetingUri: "https://meet.google.com/abc-defg-hij",
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
if (url.pathname === "/v2/spaces/space-resource-123:endActiveConference") {
|
|
338
|
+
return jsonResponse({});
|
|
339
|
+
}
|
|
340
|
+
return new Response("not found", { status: 404 });
|
|
341
|
+
});
|
|
342
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
343
|
+
|
|
344
|
+
const stdout = captureStdout();
|
|
345
|
+
try {
|
|
346
|
+
await setupCli({}).parseAsync(
|
|
347
|
+
[
|
|
348
|
+
"googlemeet",
|
|
349
|
+
"end-active-conference",
|
|
350
|
+
"https://meet.google.com/abc-defg-hij",
|
|
351
|
+
"--access-token",
|
|
352
|
+
"token",
|
|
353
|
+
"--expires-at",
|
|
354
|
+
String(Date.now() + 120_000),
|
|
355
|
+
"--json",
|
|
356
|
+
],
|
|
357
|
+
{ from: "user" },
|
|
358
|
+
);
|
|
359
|
+
expect(JSON.parse(stdout.output())).toMatchObject({
|
|
360
|
+
space: "spaces/space-resource-123",
|
|
361
|
+
ended: true,
|
|
362
|
+
tokenSource: "cached-access-token",
|
|
363
|
+
});
|
|
364
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
365
|
+
"https://meet.googleapis.com/v2/spaces/space-resource-123:endActiveConference",
|
|
366
|
+
expect.objectContaining({ method: "POST", body: "{}" }),
|
|
367
|
+
);
|
|
368
|
+
} finally {
|
|
369
|
+
stdout.restore();
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it("rejects access policy flags when create would use browser fallback", async () => {
|
|
374
|
+
await expect(
|
|
375
|
+
setupCli({
|
|
376
|
+
runtime: {
|
|
377
|
+
createViaBrowser: vi.fn(async () => {
|
|
378
|
+
throw new Error("browser fallback should not run");
|
|
379
|
+
}),
|
|
380
|
+
},
|
|
381
|
+
}).parseAsync(["googlemeet", "create", "--access-type", "OPEN"], { from: "user" }),
|
|
382
|
+
).rejects.toThrow("access policy options require OAuth/API room creation");
|
|
383
|
+
});
|
|
384
|
+
|
|
327
385
|
it("prints the latest conference record", async () => {
|
|
328
386
|
stubMeetArtifactsApi();
|
|
329
387
|
const stdout = captureStdout();
|
|
@@ -631,6 +689,55 @@ describe("google-meet CLI", () => {
|
|
|
631
689
|
}
|
|
632
690
|
});
|
|
633
691
|
|
|
692
|
+
it("runs a listen-first health probe", async () => {
|
|
693
|
+
const testListen = vi.fn(async () => ({
|
|
694
|
+
createdSession: true,
|
|
695
|
+
listenVerified: true,
|
|
696
|
+
listenTimedOut: false,
|
|
697
|
+
transcriptLines: 1,
|
|
698
|
+
session: {
|
|
699
|
+
id: "meet_1",
|
|
700
|
+
url: "https://meet.google.com/abc-defg-hij",
|
|
701
|
+
state: "active" as const,
|
|
702
|
+
transport: "chrome-node" as const,
|
|
703
|
+
mode: "transcribe" as const,
|
|
704
|
+
participantIdentity: "signed-in Google Chrome profile on a paired node",
|
|
705
|
+
createdAt: "2026-04-25T00:00:00.000Z",
|
|
706
|
+
updatedAt: "2026-04-25T00:00:01.000Z",
|
|
707
|
+
realtime: { enabled: false, provider: "openai", toolPolicy: "safe-read-only" },
|
|
708
|
+
notes: [],
|
|
709
|
+
},
|
|
710
|
+
}));
|
|
711
|
+
const stdout = captureStdout();
|
|
712
|
+
try {
|
|
713
|
+
await setupCli({
|
|
714
|
+
runtime: { testListen },
|
|
715
|
+
}).parseAsync(
|
|
716
|
+
[
|
|
717
|
+
"googlemeet",
|
|
718
|
+
"test-listen",
|
|
719
|
+
"https://meet.google.com/abc-defg-hij",
|
|
720
|
+
"--transport",
|
|
721
|
+
"chrome-node",
|
|
722
|
+
"--timeout-ms",
|
|
723
|
+
"30000",
|
|
724
|
+
],
|
|
725
|
+
{ from: "user" },
|
|
726
|
+
);
|
|
727
|
+
expect(testListen).toHaveBeenCalledWith({
|
|
728
|
+
url: "https://meet.google.com/abc-defg-hij",
|
|
729
|
+
transport: "chrome-node",
|
|
730
|
+
timeoutMs: 30000,
|
|
731
|
+
});
|
|
732
|
+
expect(JSON.parse(stdout.output())).toMatchObject({
|
|
733
|
+
listenVerified: true,
|
|
734
|
+
transcriptLines: 1,
|
|
735
|
+
});
|
|
736
|
+
} finally {
|
|
737
|
+
stdout.restore();
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
|
|
634
741
|
it("prints a dry-run export manifest without writing files", async () => {
|
|
635
742
|
stubMeetArtifactsApi();
|
|
636
743
|
const stdout = captureStdout();
|
package/src/cli.ts
CHANGED
|
@@ -10,9 +10,11 @@ import {
|
|
|
10
10
|
type GoogleMeetCalendarLookupResult,
|
|
11
11
|
} from "./calendar.js";
|
|
12
12
|
import type { GoogleMeetConfig, GoogleMeetMode, GoogleMeetTransport } from "./config.js";
|
|
13
|
+
import { hasCreateSpaceConfigInput, resolveCreateSpaceConfig } from "./create.js";
|
|
13
14
|
import {
|
|
14
15
|
buildGoogleMeetPreflightReport,
|
|
15
16
|
createGoogleMeetSpace,
|
|
17
|
+
endGoogleMeetActiveConference,
|
|
16
18
|
fetchGoogleMeetArtifacts,
|
|
17
19
|
fetchGoogleMeetAttendance,
|
|
18
20
|
fetchLatestGoogleMeetConferenceRecord,
|
|
@@ -35,6 +37,7 @@ type JoinOptions = {
|
|
|
35
37
|
transport?: GoogleMeetTransport;
|
|
36
38
|
mode?: GoogleMeetMode;
|
|
37
39
|
message?: string;
|
|
40
|
+
timeoutMs?: string;
|
|
38
41
|
dialInNumber?: string;
|
|
39
42
|
pin?: string;
|
|
40
43
|
dtmfSequence?: string;
|
|
@@ -159,6 +162,8 @@ type CreateOptions = {
|
|
|
159
162
|
clientId?: string;
|
|
160
163
|
clientSecret?: string;
|
|
161
164
|
expiresAt?: string;
|
|
165
|
+
accessType?: string;
|
|
166
|
+
entryPointAccess?: string;
|
|
162
167
|
join?: boolean;
|
|
163
168
|
transport?: GoogleMeetTransport;
|
|
164
169
|
mode?: GoogleMeetMode;
|
|
@@ -224,6 +229,17 @@ function formatOptional(value: unknown): string {
|
|
|
224
229
|
return typeof value === "string" && value.trim() ? value : "n/a";
|
|
225
230
|
}
|
|
226
231
|
|
|
232
|
+
function parsePositiveNumber(value: string | undefined, label: string): number | undefined {
|
|
233
|
+
if (value === undefined) {
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
const parsed = Number(value);
|
|
237
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
238
|
+
throw new Error(`${label} must be a positive number`);
|
|
239
|
+
}
|
|
240
|
+
return parsed;
|
|
241
|
+
}
|
|
242
|
+
|
|
227
243
|
function formatDuration(value: number | undefined): string {
|
|
228
244
|
if (value === undefined) {
|
|
229
245
|
return "n/a";
|
|
@@ -1037,7 +1053,7 @@ function renderTranscriptMarkdown(result: GoogleMeetArtifactsResult): string {
|
|
|
1037
1053
|
return `${lines.join("\n")}\n`;
|
|
1038
1054
|
}
|
|
1039
1055
|
|
|
1040
|
-
|
|
1056
|
+
function collectGoogleMeetArtifactWarnings(
|
|
1041
1057
|
result: GoogleMeetArtifactsResult,
|
|
1042
1058
|
): GoogleMeetExportWarning[] {
|
|
1043
1059
|
const warnings: GoogleMeetExportWarning[] = [];
|
|
@@ -1367,6 +1383,14 @@ export function registerGoogleMeetCli(params: {
|
|
|
1367
1383
|
.option("--client-id <id>", "OAuth client id override")
|
|
1368
1384
|
.option("--client-secret <secret>", "OAuth client secret override")
|
|
1369
1385
|
.option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds")
|
|
1386
|
+
.option(
|
|
1387
|
+
"--access-type <type>",
|
|
1388
|
+
"Google Meet SpaceConfig accessType for API create: OPEN, TRUSTED, or RESTRICTED",
|
|
1389
|
+
)
|
|
1390
|
+
.option(
|
|
1391
|
+
"--entry-point-access <type>",
|
|
1392
|
+
"Google Meet SpaceConfig entryPointAccess for API create: ALL or CREATOR_APP_ONLY",
|
|
1393
|
+
)
|
|
1370
1394
|
.option("--no-join", "Only create the meeting URL; do not join it")
|
|
1371
1395
|
.option("--transport <transport>", "Join transport: chrome, chrome-node, or twilio")
|
|
1372
1396
|
.option(
|
|
@@ -1380,6 +1404,11 @@ export function registerGoogleMeetCli(params: {
|
|
|
1380
1404
|
.option("--json", "Print JSON output", false)
|
|
1381
1405
|
.action(async (options: CreateOptions) => {
|
|
1382
1406
|
if (!hasCreateOAuth(params.config, options)) {
|
|
1407
|
+
if (hasCreateSpaceConfigInput(options as Record<string, unknown>)) {
|
|
1408
|
+
throw new Error(
|
|
1409
|
+
"Google Meet access policy options require OAuth/API room creation. Configure Google Meet OAuth or remove --access-type/--entry-point-access.",
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1383
1412
|
const rt = await params.ensureRuntime();
|
|
1384
1413
|
const result = await rt.createViaBrowser();
|
|
1385
1414
|
const join =
|
|
@@ -1423,7 +1452,10 @@ export function registerGoogleMeetCli(params: {
|
|
|
1423
1452
|
const token = await resolveGoogleMeetAccessToken(
|
|
1424
1453
|
resolveCreateTokenOptions(params.config, options),
|
|
1425
1454
|
);
|
|
1426
|
-
const result = await createGoogleMeetSpace({
|
|
1455
|
+
const result = await createGoogleMeetSpace({
|
|
1456
|
+
accessToken: token.accessToken,
|
|
1457
|
+
config: resolveCreateSpaceConfig(options as Record<string, unknown>),
|
|
1458
|
+
});
|
|
1427
1459
|
const join =
|
|
1428
1460
|
options.join !== false
|
|
1429
1461
|
? await (
|
|
@@ -1463,6 +1495,39 @@ export function registerGoogleMeetCli(params: {
|
|
|
1463
1495
|
}
|
|
1464
1496
|
});
|
|
1465
1497
|
|
|
1498
|
+
root
|
|
1499
|
+
.command("end-active-conference")
|
|
1500
|
+
.description("End the active conference for a Google Meet space")
|
|
1501
|
+
.argument("[meeting]", "Meet URL, meeting code, or spaces/{id}")
|
|
1502
|
+
.option("--access-token <token>", "Access token override")
|
|
1503
|
+
.option("--refresh-token <token>", "Refresh token override")
|
|
1504
|
+
.option("--client-id <id>", "OAuth client id override")
|
|
1505
|
+
.option("--client-secret <secret>", "OAuth client secret override")
|
|
1506
|
+
.option("--expires-at <ms>", "Cached access token expiry as unix epoch milliseconds")
|
|
1507
|
+
.option("--json", "Print JSON output", false)
|
|
1508
|
+
.action(async (meeting: string | undefined, options: ResolveSpaceOptions & JsonOptions) => {
|
|
1509
|
+
const token = await resolveGoogleMeetAccessToken(
|
|
1510
|
+
resolveOAuthTokenOptions(params.config, options),
|
|
1511
|
+
);
|
|
1512
|
+
const result = await endGoogleMeetActiveConference({
|
|
1513
|
+
accessToken: token.accessToken,
|
|
1514
|
+
meeting: resolveMeetingInput(params.config, meeting ?? options.meeting),
|
|
1515
|
+
});
|
|
1516
|
+
if (options.json) {
|
|
1517
|
+
writeStdoutJson({
|
|
1518
|
+
...result,
|
|
1519
|
+
tokenSource: token.refreshed ? "refresh-token" : "cached-access-token",
|
|
1520
|
+
});
|
|
1521
|
+
return;
|
|
1522
|
+
}
|
|
1523
|
+
writeStdoutLine("space: %s", result.space);
|
|
1524
|
+
writeStdoutLine("ended: yes");
|
|
1525
|
+
writeStdoutLine(
|
|
1526
|
+
"token source: %s",
|
|
1527
|
+
token.refreshed ? "refresh-token" : "cached-access-token",
|
|
1528
|
+
);
|
|
1529
|
+
});
|
|
1530
|
+
|
|
1466
1531
|
root
|
|
1467
1532
|
.command("join")
|
|
1468
1533
|
.argument("[url]", "Explicit https://meet.google.com/... URL")
|
|
@@ -1514,6 +1579,22 @@ export function registerGoogleMeetCli(params: {
|
|
|
1514
1579
|
);
|
|
1515
1580
|
});
|
|
1516
1581
|
|
|
1582
|
+
root
|
|
1583
|
+
.command("test-listen")
|
|
1584
|
+
.argument("[url]", "Explicit https://meet.google.com/... URL")
|
|
1585
|
+
.option("--transport <transport>", "Transport: chrome or chrome-node")
|
|
1586
|
+
.option("--timeout-ms <ms>", "How long to wait for fresh captions/transcript movement")
|
|
1587
|
+
.action(async (url: string | undefined, options: JoinOptions) => {
|
|
1588
|
+
const rt = await params.ensureRuntime();
|
|
1589
|
+
writeStdoutJson(
|
|
1590
|
+
await rt.testListen({
|
|
1591
|
+
url: resolveMeetingInput(params.config, url),
|
|
1592
|
+
transport: options.transport,
|
|
1593
|
+
timeoutMs: parsePositiveNumber(options.timeoutMs, "timeout-ms"),
|
|
1594
|
+
}),
|
|
1595
|
+
);
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1517
1598
|
root
|
|
1518
1599
|
.command("resolve-space")
|
|
1519
1600
|
.description("Resolve a Meet URL, meeting code, or spaces/{id} to its canonical space")
|
package/src/create.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
|
|
2
2
|
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
|
3
3
|
import type { GoogleMeetConfig, GoogleMeetMode, GoogleMeetTransport } from "./config.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
createGoogleMeetSpace,
|
|
6
|
+
type GoogleMeetAccessType,
|
|
7
|
+
type GoogleMeetEntryPointAccess,
|
|
8
|
+
type GoogleMeetSpaceConfig,
|
|
9
|
+
} from "./meet.js";
|
|
5
10
|
import { resolveGoogleMeetAccessToken } from "./oauth.js";
|
|
6
11
|
import type { GoogleMeetRuntime } from "./runtime.js";
|
|
7
12
|
import { createMeetWithBrowserProxyOnNode } from "./transports/chrome-create.js";
|
|
@@ -14,6 +19,47 @@ function normalizeMode(value: unknown): GoogleMeetMode | undefined {
|
|
|
14
19
|
return value === "realtime" || value === "transcribe" ? value : undefined;
|
|
15
20
|
}
|
|
16
21
|
|
|
22
|
+
function normalizeGoogleMeetAccessType(value: unknown): GoogleMeetAccessType | undefined {
|
|
23
|
+
const normalized = normalizeOptionalString(value)?.toUpperCase().replaceAll("-", "_");
|
|
24
|
+
return normalized === "OPEN" || normalized === "TRUSTED" || normalized === "RESTRICTED"
|
|
25
|
+
? normalized
|
|
26
|
+
: undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeGoogleMeetEntryPointAccess(
|
|
30
|
+
value: unknown,
|
|
31
|
+
): GoogleMeetEntryPointAccess | undefined {
|
|
32
|
+
const normalized = normalizeOptionalString(value)?.toUpperCase().replaceAll("-", "_");
|
|
33
|
+
return normalized === "ALL" || normalized === "CREATOR_APP_ONLY" ? normalized : undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function resolveCreateSpaceConfig(
|
|
37
|
+
raw: Record<string, unknown>,
|
|
38
|
+
): GoogleMeetSpaceConfig | undefined {
|
|
39
|
+
const rawAccessType = normalizeOptionalString(raw.accessType);
|
|
40
|
+
const rawEntryPointAccess = normalizeOptionalString(raw.entryPointAccess);
|
|
41
|
+
const accessType = normalizeGoogleMeetAccessType(raw.accessType);
|
|
42
|
+
const entryPointAccess = normalizeGoogleMeetEntryPointAccess(raw.entryPointAccess);
|
|
43
|
+
if (rawAccessType !== undefined && !accessType) {
|
|
44
|
+
throw new Error("Invalid Google Meet accessType. Expected OPEN, TRUSTED, or RESTRICTED.");
|
|
45
|
+
}
|
|
46
|
+
if (rawEntryPointAccess !== undefined && !entryPointAccess) {
|
|
47
|
+
throw new Error("Invalid Google Meet entryPointAccess. Expected ALL or CREATOR_APP_ONLY.");
|
|
48
|
+
}
|
|
49
|
+
const config = {
|
|
50
|
+
...(accessType ? { accessType } : {}),
|
|
51
|
+
...(entryPointAccess ? { entryPointAccess } : {}),
|
|
52
|
+
};
|
|
53
|
+
return Object.keys(config).length > 0 ? config : undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function hasCreateSpaceConfigInput(raw: Record<string, unknown>): boolean {
|
|
57
|
+
return (
|
|
58
|
+
normalizeOptionalString(raw.accessType) !== undefined ||
|
|
59
|
+
normalizeOptionalString(raw.entryPointAccess) !== undefined
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
17
63
|
async function createSpaceFromParams(config: GoogleMeetConfig, raw: Record<string, unknown>) {
|
|
18
64
|
const token = await resolveGoogleMeetAccessToken({
|
|
19
65
|
clientId: normalizeOptionalString(raw.clientId) ?? config.oauth.clientId,
|
|
@@ -22,7 +68,10 @@ async function createSpaceFromParams(config: GoogleMeetConfig, raw: Record<strin
|
|
|
22
68
|
accessToken: normalizeOptionalString(raw.accessToken) ?? config.oauth.accessToken,
|
|
23
69
|
expiresAt: typeof raw.expiresAt === "number" ? raw.expiresAt : config.oauth.expiresAt,
|
|
24
70
|
});
|
|
25
|
-
const result = await createGoogleMeetSpace({
|
|
71
|
+
const result = await createGoogleMeetSpace({
|
|
72
|
+
accessToken: token.accessToken,
|
|
73
|
+
config: resolveCreateSpaceConfig(raw),
|
|
74
|
+
});
|
|
26
75
|
return { source: "api" as const, token, ...result };
|
|
27
76
|
}
|
|
28
77
|
|
|
@@ -35,10 +84,6 @@ function hasGoogleMeetOAuth(config: GoogleMeetConfig, raw: Record<string, unknow
|
|
|
35
84
|
);
|
|
36
85
|
}
|
|
37
86
|
|
|
38
|
-
export function shouldJoinCreatedMeet(raw: Record<string, unknown>): boolean {
|
|
39
|
-
return raw.join !== false && raw.join !== "false";
|
|
40
|
-
}
|
|
41
|
-
|
|
42
87
|
export async function createMeetFromParams(params: {
|
|
43
88
|
config: GoogleMeetConfig;
|
|
44
89
|
runtime: OpenClawPluginApi["runtime"];
|
|
@@ -53,6 +98,11 @@ export async function createMeetFromParams(params: {
|
|
|
53
98
|
"URL-only creation was requested. Call google_meet with action=join and url=meetingUri to enter the meeting.",
|
|
54
99
|
};
|
|
55
100
|
}
|
|
101
|
+
if (hasCreateSpaceConfigInput(params.raw)) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
"Google Meet access policy options require OAuth/API room creation. Configure Google Meet OAuth or remove accessType/entryPointAccess.",
|
|
104
|
+
);
|
|
105
|
+
}
|
|
56
106
|
const browser = await createMeetWithBrowserProxyOnNode({
|
|
57
107
|
runtime: params.runtime,
|
|
58
108
|
config: params.config,
|
package/src/meet.ts
CHANGED
|
@@ -9,16 +9,26 @@ const GOOGLE_MEET_API_HOST = "meet.googleapis.com";
|
|
|
9
9
|
const GOOGLE_MEET_MEDIA_SCOPE =
|
|
10
10
|
"https://www.googleapis.com/auth/meetings.conference.media.readonly";
|
|
11
11
|
const GOOGLE_MEET_SPACE_SCOPE = "https://www.googleapis.com/auth/meetings.space.readonly";
|
|
12
|
+
const GOOGLE_MEET_SPACE_CREATED_SCOPE = "https://www.googleapis.com/auth/meetings.space.created";
|
|
13
|
+
const GOOGLE_MEET_SPACE_SETTINGS_SCOPE = "https://www.googleapis.com/auth/meetings.space.settings";
|
|
12
14
|
|
|
13
|
-
type
|
|
15
|
+
export type GoogleMeetAccessType = "OPEN" | "TRUSTED" | "RESTRICTED";
|
|
16
|
+
export type GoogleMeetEntryPointAccess = "ALL" | "CREATOR_APP_ONLY";
|
|
17
|
+
|
|
18
|
+
export type GoogleMeetSpaceConfig = {
|
|
19
|
+
accessType?: GoogleMeetAccessType;
|
|
20
|
+
entryPointAccess?: GoogleMeetEntryPointAccess;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type GoogleMeetSpace = {
|
|
14
24
|
name: string;
|
|
15
25
|
meetingCode?: string;
|
|
16
26
|
meetingUri?: string;
|
|
17
27
|
activeConference?: Record<string, unknown>;
|
|
18
|
-
config?: Record<string, unknown>;
|
|
28
|
+
config?: GoogleMeetSpaceConfig & Record<string, unknown>;
|
|
19
29
|
};
|
|
20
30
|
|
|
21
|
-
type GoogleMeetPreflightReport = {
|
|
31
|
+
export type GoogleMeetPreflightReport = {
|
|
22
32
|
input: string;
|
|
23
33
|
resolvedSpaceName: string;
|
|
24
34
|
meetingCode?: string;
|
|
@@ -29,12 +39,17 @@ type GoogleMeetPreflightReport = {
|
|
|
29
39
|
blockers: string[];
|
|
30
40
|
};
|
|
31
41
|
|
|
32
|
-
type GoogleMeetCreateSpaceResult = {
|
|
42
|
+
export type GoogleMeetCreateSpaceResult = {
|
|
33
43
|
space: GoogleMeetSpace;
|
|
34
44
|
meetingUri: string;
|
|
35
45
|
};
|
|
36
46
|
|
|
37
|
-
type
|
|
47
|
+
export type GoogleMeetEndActiveConferenceResult = {
|
|
48
|
+
space: string;
|
|
49
|
+
ended: true;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type GoogleMeetConferenceRecord = {
|
|
38
53
|
name: string;
|
|
39
54
|
space?: string;
|
|
40
55
|
startTime?: string;
|
|
@@ -353,7 +368,12 @@ export async function fetchGoogleMeetSpace(params: {
|
|
|
353
368
|
|
|
354
369
|
export async function createGoogleMeetSpace(params: {
|
|
355
370
|
accessToken: string;
|
|
371
|
+
config?: GoogleMeetSpaceConfig;
|
|
356
372
|
}): Promise<GoogleMeetCreateSpaceResult> {
|
|
373
|
+
const body =
|
|
374
|
+
params.config && Object.keys(params.config).length > 0
|
|
375
|
+
? JSON.stringify({ config: params.config })
|
|
376
|
+
: "{}";
|
|
357
377
|
const { response, release } = await fetchWithSsrFGuard({
|
|
358
378
|
url: `${GOOGLE_MEET_API_BASE_URL}/spaces`,
|
|
359
379
|
init: {
|
|
@@ -363,7 +383,7 @@ export async function createGoogleMeetSpace(params: {
|
|
|
363
383
|
Accept: "application/json",
|
|
364
384
|
"Content-Type": "application/json",
|
|
365
385
|
},
|
|
366
|
-
body
|
|
386
|
+
body,
|
|
367
387
|
},
|
|
368
388
|
policy: { allowedHostnames: [GOOGLE_MEET_API_HOST] },
|
|
369
389
|
auditContext: "google-meet.spaces.create",
|
|
@@ -375,7 +395,10 @@ export async function createGoogleMeetSpace(params: {
|
|
|
375
395
|
response,
|
|
376
396
|
detail,
|
|
377
397
|
prefix: "Google Meet spaces.create",
|
|
378
|
-
scopes:
|
|
398
|
+
scopes:
|
|
399
|
+
params.config && Object.keys(params.config).length > 0
|
|
400
|
+
? [GOOGLE_MEET_SPACE_CREATED_SCOPE, GOOGLE_MEET_SPACE_SETTINGS_SCOPE]
|
|
401
|
+
: [GOOGLE_MEET_SPACE_CREATED_SCOPE],
|
|
379
402
|
});
|
|
380
403
|
}
|
|
381
404
|
const payload = (await response.json()) as GoogleMeetSpace;
|
|
@@ -392,6 +415,45 @@ export async function createGoogleMeetSpace(params: {
|
|
|
392
415
|
}
|
|
393
416
|
}
|
|
394
417
|
|
|
418
|
+
export async function endGoogleMeetActiveConference(params: {
|
|
419
|
+
accessToken: string;
|
|
420
|
+
meeting: string;
|
|
421
|
+
}): Promise<GoogleMeetEndActiveConferenceResult> {
|
|
422
|
+
const resolved = await fetchGoogleMeetSpace({
|
|
423
|
+
accessToken: params.accessToken,
|
|
424
|
+
meeting: params.meeting,
|
|
425
|
+
});
|
|
426
|
+
const space = resolved.name;
|
|
427
|
+
const { response, release } = await fetchWithSsrFGuard({
|
|
428
|
+
url: `${GOOGLE_MEET_API_BASE_URL}/${encodeSpaceNameForPath(space)}:endActiveConference`,
|
|
429
|
+
init: {
|
|
430
|
+
method: "POST",
|
|
431
|
+
headers: {
|
|
432
|
+
Authorization: `Bearer ${params.accessToken}`,
|
|
433
|
+
Accept: "application/json",
|
|
434
|
+
"Content-Type": "application/json",
|
|
435
|
+
},
|
|
436
|
+
body: "{}",
|
|
437
|
+
},
|
|
438
|
+
policy: { allowedHostnames: [GOOGLE_MEET_API_HOST] },
|
|
439
|
+
auditContext: "google-meet.spaces.endActiveConference",
|
|
440
|
+
});
|
|
441
|
+
try {
|
|
442
|
+
if (!response.ok) {
|
|
443
|
+
const detail = await response.text();
|
|
444
|
+
throw await googleApiError({
|
|
445
|
+
response,
|
|
446
|
+
detail,
|
|
447
|
+
prefix: "Google Meet spaces.endActiveConference",
|
|
448
|
+
scopes: [GOOGLE_MEET_SPACE_CREATED_SCOPE],
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
return { space, ended: true };
|
|
452
|
+
} finally {
|
|
453
|
+
await release();
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
395
457
|
async function fetchGoogleMeetConferenceRecord(params: {
|
|
396
458
|
accessToken: string;
|
|
397
459
|
conferenceRecord: string;
|
package/src/oauth.ts
CHANGED
|
@@ -6,13 +6,14 @@ import {
|
|
|
6
6
|
} from "openclaw/plugin-sdk/provider-auth-runtime";
|
|
7
7
|
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
const GOOGLE_MEET_REDIRECT_URI = "http://localhost:8085/oauth2callback";
|
|
10
|
+
const GOOGLE_MEET_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
11
|
+
const GOOGLE_MEET_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
12
12
|
const GOOGLE_MEET_TOKEN_HOST = "oauth2.googleapis.com";
|
|
13
|
-
|
|
13
|
+
const GOOGLE_MEET_SCOPES = [
|
|
14
14
|
"https://www.googleapis.com/auth/meetings.space.created",
|
|
15
15
|
"https://www.googleapis.com/auth/meetings.space.readonly",
|
|
16
|
+
"https://www.googleapis.com/auth/meetings.space.settings",
|
|
16
17
|
"https://www.googleapis.com/auth/meetings.conference.media.readonly",
|
|
17
18
|
"https://www.googleapis.com/auth/calendar.events.readonly",
|
|
18
19
|
"https://www.googleapis.com/auth/drive.meet.readonly",
|
|
@@ -137,7 +138,7 @@ export async function refreshGoogleMeetAccessToken(params: {
|
|
|
137
138
|
);
|
|
138
139
|
}
|
|
139
140
|
|
|
140
|
-
|
|
141
|
+
function shouldUseCachedGoogleMeetAccessToken(params: {
|
|
141
142
|
accessToken?: string;
|
|
142
143
|
expiresAt?: number;
|
|
143
144
|
now?: number;
|