@kodelyth/google-meet 2026.5.39 → 2026.5.42
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/dist/calendar-CEiBGUl2.js +136 -0
- package/dist/chrome-create-DMyPCiEd.js +965 -0
- package/dist/cli-BMVhSIb8.js +1390 -0
- package/dist/create-IbyMXB3V.js +108 -0
- package/dist/doctor-contract-api.js +56 -0
- package/dist/index.js +4979 -0
- package/dist/oauth-7_sWAae1.js +141 -0
- package/doctor-contract-api.ts +1 -0
- package/google-meet.live.test.ts +82 -0
- package/index.create.test.ts +671 -0
- package/index.test.ts +5051 -0
- package/index.ts +1224 -0
- package/klaw.plugin.json +12 -46
- package/node-host.test.ts +241 -0
- package/package.json +3 -3
- package/src/agent-consult.ts +158 -0
- package/src/calendar.ts +252 -0
- package/src/cli.test.ts +1234 -0
- package/src/cli.ts +2350 -0
- package/src/config-compat.test.ts +98 -0
- package/src/config-compat.ts +84 -0
- package/src/config.ts +589 -0
- package/src/create.ts +157 -0
- package/src/drive.ts +72 -0
- package/src/google-api-errors.ts +20 -0
- package/src/meet.ts +1024 -0
- package/src/node-host.ts +520 -0
- package/src/oauth.test.ts +73 -0
- package/src/oauth.ts +229 -0
- package/src/realtime-node.ts +780 -0
- package/src/realtime.ts +1334 -0
- package/src/runtime.ts +1008 -0
- package/src/setup.ts +285 -0
- package/src/test-support/plugin-harness.ts +232 -0
- package/src/transports/chrome-browser-proxy.test.ts +39 -0
- package/src/transports/chrome-browser-proxy.ts +204 -0
- package/src/transports/chrome-create.ts +364 -0
- package/src/transports/chrome.test.ts +12 -0
- package/src/transports/chrome.ts +1065 -0
- package/src/transports/twilio.ts +57 -0
- package/src/transports/types.ts +147 -0
- package/src/voice-call-gateway.test.ts +152 -0
- package/src/voice-call-gateway.ts +241 -0
- package/tsconfig.json +16 -0
- package/doctor-contract-api.js +0 -7
- package/index.js +0 -7
package/src/calendar.ts
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { fetchWithSsrFGuard } from "klaw/plugin-sdk/ssrf-runtime";
|
|
2
|
+
import { googleApiError } from "./google-api-errors.js";
|
|
3
|
+
|
|
4
|
+
const GOOGLE_CALENDAR_API_BASE_URL = "https://www.googleapis.com/calendar/v3";
|
|
5
|
+
const GOOGLE_CALENDAR_API_HOST = "www.googleapis.com";
|
|
6
|
+
const GOOGLE_MEET_URL_HOST = "meet.google.com";
|
|
7
|
+
const GOOGLE_CALENDAR_EVENTS_SCOPE = "https://www.googleapis.com/auth/calendar.events.readonly";
|
|
8
|
+
|
|
9
|
+
type GoogleCalendarEventDate = {
|
|
10
|
+
date?: string;
|
|
11
|
+
dateTime?: string;
|
|
12
|
+
timeZone?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type GoogleCalendarConferenceEntryPoint = {
|
|
16
|
+
entryPointType?: string;
|
|
17
|
+
uri?: string;
|
|
18
|
+
label?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type GoogleMeetCalendarEvent = {
|
|
22
|
+
id?: string;
|
|
23
|
+
summary?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
location?: string;
|
|
26
|
+
status?: string;
|
|
27
|
+
htmlLink?: string;
|
|
28
|
+
hangoutLink?: string;
|
|
29
|
+
start?: GoogleCalendarEventDate;
|
|
30
|
+
end?: GoogleCalendarEventDate;
|
|
31
|
+
conferenceData?: {
|
|
32
|
+
conferenceId?: string;
|
|
33
|
+
conferenceSolution?: {
|
|
34
|
+
key?: { type?: string };
|
|
35
|
+
name?: string;
|
|
36
|
+
};
|
|
37
|
+
entryPoints?: GoogleCalendarConferenceEntryPoint[];
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type GoogleMeetCalendarLookupResult = {
|
|
42
|
+
calendarId: string;
|
|
43
|
+
event: GoogleMeetCalendarEvent;
|
|
44
|
+
meetingUri: string;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
type GoogleMeetCalendarEventsResult = {
|
|
48
|
+
calendarId: string;
|
|
49
|
+
events: Array<{
|
|
50
|
+
event: GoogleMeetCalendarEvent;
|
|
51
|
+
meetingUri: string;
|
|
52
|
+
selected: boolean;
|
|
53
|
+
}>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function appendQuery(url: string, query: Record<string, string | number | boolean | undefined>) {
|
|
57
|
+
const parsed = new URL(url);
|
|
58
|
+
for (const [key, value] of Object.entries(query)) {
|
|
59
|
+
if (value !== undefined) {
|
|
60
|
+
parsed.searchParams.set(key, String(value));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return parsed.toString();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isGoogleMeetUri(value: string | undefined): value is string {
|
|
67
|
+
if (!value?.trim()) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
return new URL(value).hostname === GOOGLE_MEET_URL_HOST;
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function extractGoogleMeetUriFromText(value: string | undefined): string | undefined {
|
|
78
|
+
const match = value?.match(/https:\/\/meet\.google\.com\/[a-z0-9-]+/i);
|
|
79
|
+
return match?.[0];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function extractGoogleMeetUriFromCalendarEvent(
|
|
83
|
+
event: GoogleMeetCalendarEvent,
|
|
84
|
+
): string | undefined {
|
|
85
|
+
if (isGoogleMeetUri(event.hangoutLink)) {
|
|
86
|
+
return event.hangoutLink;
|
|
87
|
+
}
|
|
88
|
+
const entryPoints = event.conferenceData?.entryPoints ?? [];
|
|
89
|
+
const videoEntry = entryPoints.find(
|
|
90
|
+
(entry) => entry.entryPointType === "video" && isGoogleMeetUri(entry.uri),
|
|
91
|
+
);
|
|
92
|
+
if (videoEntry?.uri) {
|
|
93
|
+
return videoEntry.uri;
|
|
94
|
+
}
|
|
95
|
+
const meetEntry = entryPoints.find((entry) => isGoogleMeetUri(entry.uri));
|
|
96
|
+
if (meetEntry?.uri) {
|
|
97
|
+
return meetEntry.uri;
|
|
98
|
+
}
|
|
99
|
+
return (
|
|
100
|
+
extractGoogleMeetUriFromText(event.location) ?? extractGoogleMeetUriFromText(event.description)
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function buildGoogleMeetCalendarDayWindow(now = new Date()): {
|
|
105
|
+
timeMin: string;
|
|
106
|
+
timeMax: string;
|
|
107
|
+
} {
|
|
108
|
+
const start = new Date(now);
|
|
109
|
+
start.setHours(0, 0, 0, 0);
|
|
110
|
+
const end = new Date(start);
|
|
111
|
+
end.setDate(start.getDate() + 1);
|
|
112
|
+
return { timeMin: start.toISOString(), timeMax: end.toISOString() };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function parseCalendarEventTime(value: GoogleCalendarEventDate | undefined): number | undefined {
|
|
116
|
+
const raw = value?.dateTime ?? value?.date;
|
|
117
|
+
if (!raw) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
const parsed = Date.parse(raw);
|
|
121
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function rankCalendarEvent(event: GoogleMeetCalendarEvent, nowMs: number): number {
|
|
125
|
+
const startMs = parseCalendarEventTime(event.start) ?? Number.POSITIVE_INFINITY;
|
|
126
|
+
const endMs = parseCalendarEventTime(event.end) ?? startMs;
|
|
127
|
+
if (startMs <= nowMs && endMs >= nowMs) {
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
if (startMs > nowMs) {
|
|
131
|
+
return startMs - nowMs;
|
|
132
|
+
}
|
|
133
|
+
return nowMs - startMs + 30 * 24 * 60 * 60 * 1000;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function chooseBestMeetCalendarEvent(
|
|
137
|
+
events: GoogleMeetCalendarEvent[],
|
|
138
|
+
now: Date,
|
|
139
|
+
): GoogleMeetCalendarLookupResult["event"] | undefined {
|
|
140
|
+
const nowMs = now.getTime();
|
|
141
|
+
let selected: GoogleMeetCalendarEvent | undefined;
|
|
142
|
+
let selectedRank = Number.POSITIVE_INFINITY;
|
|
143
|
+
for (const event of events) {
|
|
144
|
+
if (event.status === "cancelled" || !extractGoogleMeetUriFromCalendarEvent(event)) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const rank = rankCalendarEvent(event, nowMs);
|
|
148
|
+
if (!selected || rank < selectedRank) {
|
|
149
|
+
selected = event;
|
|
150
|
+
selectedRank = rank;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return selected;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function fetchGoogleCalendarEvents(params: {
|
|
157
|
+
accessToken: string;
|
|
158
|
+
calendarId?: string;
|
|
159
|
+
eventQuery?: string;
|
|
160
|
+
timeMin?: string;
|
|
161
|
+
timeMax?: string;
|
|
162
|
+
maxResults?: number;
|
|
163
|
+
now?: Date;
|
|
164
|
+
}): Promise<{ calendarId: string; events: GoogleMeetCalendarEvent[]; now: Date }> {
|
|
165
|
+
const calendarId = params.calendarId?.trim() || "primary";
|
|
166
|
+
const now = params.now ?? new Date();
|
|
167
|
+
const defaultTimeMax = new Date(now);
|
|
168
|
+
defaultTimeMax.setDate(defaultTimeMax.getDate() + 7);
|
|
169
|
+
const { response, release } = await fetchWithSsrFGuard({
|
|
170
|
+
url: appendQuery(
|
|
171
|
+
`${GOOGLE_CALENDAR_API_BASE_URL}/calendars/${encodeURIComponent(calendarId)}/events`,
|
|
172
|
+
{
|
|
173
|
+
maxResults: params.maxResults ?? 50,
|
|
174
|
+
orderBy: "startTime",
|
|
175
|
+
q: params.eventQuery?.trim() || undefined,
|
|
176
|
+
showDeleted: false,
|
|
177
|
+
singleEvents: true,
|
|
178
|
+
timeMin: params.timeMin ?? now.toISOString(),
|
|
179
|
+
timeMax: params.timeMax ?? defaultTimeMax.toISOString(),
|
|
180
|
+
},
|
|
181
|
+
),
|
|
182
|
+
init: {
|
|
183
|
+
headers: {
|
|
184
|
+
Authorization: `Bearer ${params.accessToken}`,
|
|
185
|
+
Accept: "application/json",
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
policy: { allowedHostnames: [GOOGLE_CALENDAR_API_HOST] },
|
|
189
|
+
auditContext: "google-meet.calendar.events.list",
|
|
190
|
+
});
|
|
191
|
+
try {
|
|
192
|
+
if (!response.ok) {
|
|
193
|
+
const detail = await response.text();
|
|
194
|
+
throw await googleApiError({
|
|
195
|
+
response,
|
|
196
|
+
detail,
|
|
197
|
+
prefix: "Google Calendar events.list",
|
|
198
|
+
scopes: [GOOGLE_CALENDAR_EVENTS_SCOPE],
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
const payload = (await response.json()) as { items?: unknown };
|
|
202
|
+
if (payload.items !== undefined && !Array.isArray(payload.items)) {
|
|
203
|
+
throw new Error("Google Calendar events.list response had non-array items");
|
|
204
|
+
}
|
|
205
|
+
return { calendarId, events: (payload.items ?? []) as GoogleMeetCalendarEvent[], now };
|
|
206
|
+
} finally {
|
|
207
|
+
await release();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export async function listGoogleMeetCalendarEvents(params: {
|
|
212
|
+
accessToken: string;
|
|
213
|
+
calendarId?: string;
|
|
214
|
+
eventQuery?: string;
|
|
215
|
+
timeMin?: string;
|
|
216
|
+
timeMax?: string;
|
|
217
|
+
maxResults?: number;
|
|
218
|
+
now?: Date;
|
|
219
|
+
}): Promise<GoogleMeetCalendarEventsResult> {
|
|
220
|
+
const { calendarId, events, now } = await fetchGoogleCalendarEvents(params);
|
|
221
|
+
const best = chooseBestMeetCalendarEvent(events, now);
|
|
222
|
+
return {
|
|
223
|
+
calendarId,
|
|
224
|
+
events: events
|
|
225
|
+
.map((event) => {
|
|
226
|
+
const meetingUri = extractGoogleMeetUriFromCalendarEvent(event);
|
|
227
|
+
return meetingUri ? { event, meetingUri, selected: event === best } : undefined;
|
|
228
|
+
})
|
|
229
|
+
.filter((event): event is GoogleMeetCalendarEventsResult["events"][number] => Boolean(event)),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function findGoogleMeetCalendarEvent(params: {
|
|
234
|
+
accessToken: string;
|
|
235
|
+
calendarId?: string;
|
|
236
|
+
eventQuery?: string;
|
|
237
|
+
timeMin?: string;
|
|
238
|
+
timeMax?: string;
|
|
239
|
+
maxResults?: number;
|
|
240
|
+
now?: Date;
|
|
241
|
+
}): Promise<GoogleMeetCalendarLookupResult> {
|
|
242
|
+
const result = await listGoogleMeetCalendarEvents(params);
|
|
243
|
+
const selected = result.events.find((event) => event.selected) ?? result.events[0];
|
|
244
|
+
if (!selected) {
|
|
245
|
+
throw new Error("No Google Calendar event with a Google Meet link matched the query");
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
calendarId: result.calendarId,
|
|
249
|
+
event: selected.event,
|
|
250
|
+
meetingUri: selected.meetingUri,
|
|
251
|
+
};
|
|
252
|
+
}
|