feat: Reduce bundle size via better imports + install gcal + gvideo by default for google signups (#17810)
* wip * use light imports * fix * perf: Reduce bundle size via better imports with `googleapis` (#17811) * fix test * refactor * update vite.config.js * fix * fix unit test failing
This commit is contained in:
+1
-1
@@ -119,7 +119,7 @@ NEXT_PUBLIC_FRESHCHAT_HOST=
|
||||
|
||||
# Google OAuth credentials
|
||||
# To enable Login with Google you need to:
|
||||
# 1. Set `GOOGLE_API_CREDENTIALS` above
|
||||
# 1. Set `GOOGLE_API_CREDENTIALS` below
|
||||
# 2. Set `GOOGLE_LOGIN_ENABLED` to `true`
|
||||
# When self-hosting please ensure you configure the Google integration as an Internal app so no one else can login to your instance
|
||||
# @see https://support.google.com/cloud/answer/6158849#public-and-internal&zippy=%2Cpublic-and-internal-applications
|
||||
|
||||
@@ -10,7 +10,8 @@ import { Injectable } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { Request } from "express";
|
||||
import { google } from "googleapis";
|
||||
import { OAuth2Client } from "googleapis-common";
|
||||
import { calendar_v3 } from "@googleapis/calendar";
|
||||
import { z } from "zod";
|
||||
|
||||
import { SUCCESS_STATUS, GOOGLE_CALENDAR_TYPE } from "@calcom/platform-constants";
|
||||
@@ -78,7 +79,7 @@ export class GoogleCalendarService implements OAuthCalendarApp {
|
||||
|
||||
const { client_id, client_secret } = this.gcalResponseSchema.parse(app.keys);
|
||||
|
||||
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirectUri);
|
||||
const oAuth2Client = new OAuth2Client(client_id, client_secret, redirectUri);
|
||||
return oAuth2Client;
|
||||
}
|
||||
|
||||
@@ -134,8 +135,7 @@ export class GoogleCalendarService implements OAuthCalendarApp {
|
||||
|
||||
oAuth2Client.setCredentials(key);
|
||||
|
||||
const calendar = google.calendar({
|
||||
version: "v3",
|
||||
const calendar = new calendar_v3.Calendar({
|
||||
auth: oAuth2Client,
|
||||
});
|
||||
|
||||
|
||||
@@ -27,10 +27,7 @@ import {
|
||||
} from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { Request } from "express";
|
||||
import { google } from "googleapis";
|
||||
import { z } from "zod";
|
||||
|
||||
import { APPS_READ, GOOGLE_CALENDAR_TYPE, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AppsRepository } from "@/modules/apps/apps.repository";
|
||||
import { Injectable, Logger, NotFoundException } from "@nestjs/common";
|
||||
import { google } from "googleapis";
|
||||
import { OAuth2Client } from "googleapis-common";
|
||||
import { z } from "zod";
|
||||
|
||||
@Injectable()
|
||||
@@ -21,7 +21,7 @@ export class GCalService {
|
||||
|
||||
const { client_id, client_secret } = this.gcalResponseSchema.parse(app.keys);
|
||||
|
||||
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirectUri);
|
||||
const oAuth2Client = new OAuth2Client(client_id, client_secret, redirectUri);
|
||||
return oAuth2Client;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,9 @@
|
||||
"@dub/analytics": "^0.0.15",
|
||||
"@formkit/auto-animate": "1.0.0-beta.5",
|
||||
"@glidejs/glide": "^3.5.2",
|
||||
"@googleapis/admin": "^23.0.0",
|
||||
"@googleapis/calendar": "^9.7.9",
|
||||
"@googleapis/oauth2": "^1.0.7",
|
||||
"@hookform/error-message": "^2.0.0",
|
||||
"@hookform/resolvers": "^2.9.7",
|
||||
"@next-auth/prisma-adapter": "^1.0.4",
|
||||
@@ -85,7 +88,6 @@
|
||||
"dotenv-cli": "^6.0.0",
|
||||
"entities": "^4.4.0",
|
||||
"eslint-config-next": "^13.2.1",
|
||||
"googleapis": "^84.0.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"handlebars": "^4.7.7",
|
||||
"ical.js": "^1.4.0",
|
||||
@@ -180,6 +182,7 @@
|
||||
"deasync": "^0.1.30",
|
||||
"detect-port": "^1.3.0",
|
||||
"env-cmd": "^10.1.0",
|
||||
"google-auth-library": "^9.15.0",
|
||||
"module-alias": "^2.2.2",
|
||||
"msw": "^0.42.3",
|
||||
"node-html-parser": "^6.1.10",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { google } from "googleapis";
|
||||
import { OAuth2Client } from "googleapis-common";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import getAppKeysFromSlug from "@calcom/app-store/_utils/getAppKeysFromSlug";
|
||||
@@ -20,7 +20,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
// use differnt callback to normal calendar connection
|
||||
const redirect_uri = `${WEBAPP_URL}/api/teams/googleworkspace/callback`;
|
||||
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uri);
|
||||
const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uri);
|
||||
|
||||
const authUrl = oAuth2Client.generateAuthUrl({
|
||||
access_type: "offline",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { google } from "googleapis";
|
||||
import { OAuth2Client } from "googleapis-common";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -37,7 +37,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
return res.status(400).json({ message: "Google client_secret missing." });
|
||||
|
||||
const redirect_uri = `${WEBAPP_URL}/api/teams/googleworkspace/callback`;
|
||||
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uri);
|
||||
const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uri);
|
||||
|
||||
if (!code) {
|
||||
throw new Error("No code provided");
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { oauth2_v2 } from "@googleapis/oauth2";
|
||||
import type { OAuth2Client } from "googleapis-common";
|
||||
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { UserRepository } from "@calcom/lib/server/repository/user";
|
||||
|
||||
export async function updateProfilePhotoGoogle(oAuth2Client: OAuth2Client, userId: number) {
|
||||
try {
|
||||
const oauth2 = new oauth2_v2.Oauth2({ auth: oAuth2Client });
|
||||
const userDetails = await oauth2.userinfo.get();
|
||||
if (userDetails.data?.picture) {
|
||||
await UserRepository.updateAvatar({ id: userId, avatarUrl: userDetails.data.picture });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error updating avatarUrl from google calendar connect", error);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,21 @@
|
||||
import { google } from "googleapis";
|
||||
import { OAuth2Client } from "googleapis-common";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { WEBAPP_URL_FOR_OAUTH } from "@calcom/lib/constants";
|
||||
import { GOOGLE_CALENDAR_SCOPES, SCOPE_USERINFO_PROFILE, WEBAPP_URL_FOR_OAUTH } from "@calcom/lib/constants";
|
||||
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
|
||||
|
||||
import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState";
|
||||
import { SCOPES } from "../lib/constants";
|
||||
import { getGoogleAppKeys } from "../lib/getGoogleAppKeys";
|
||||
|
||||
async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Get token from Google Calendar API
|
||||
const { client_id, client_secret } = await getGoogleAppKeys();
|
||||
const redirect_uri = `${WEBAPP_URL_FOR_OAUTH}/api/integrations/googlecalendar/callback`;
|
||||
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uri);
|
||||
const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uri);
|
||||
|
||||
const authUrl = oAuth2Client.generateAuthUrl({
|
||||
access_type: "offline",
|
||||
scope: SCOPES,
|
||||
scope: [SCOPE_USERINFO_PROFILE, ...GOOGLE_CALENDAR_SCOPES],
|
||||
// A refresh token is only returned the first time the user
|
||||
// consents to providing access. For illustration purposes,
|
||||
// setting the prompt to 'consent' will force this consent
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
import { google } from "googleapis";
|
||||
import { calendar_v3 } from "@googleapis/calendar";
|
||||
import { OAuth2Client } from "googleapis-common";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { updateProfilePhotoGoogle } from "@calcom/app-store/_utils/oauth/updateProfilePhotoGoogle";
|
||||
import GoogleCalendarService from "@calcom/app-store/googlecalendar/lib/CalendarService";
|
||||
import { renewSelectedCalendarCredentialId } from "@calcom/lib/connectedCalendar";
|
||||
import { WEBAPP_URL, WEBAPP_URL_FOR_OAUTH } from "@calcom/lib/constants";
|
||||
import {
|
||||
GOOGLE_CALENDAR_SCOPES,
|
||||
SCOPE_USERINFO_PROFILE,
|
||||
WEBAPP_URL,
|
||||
WEBAPP_URL_FOR_OAUTH,
|
||||
} from "@calcom/lib/constants";
|
||||
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
|
||||
import { getAllCalendars, updateProfilePhoto } from "@calcom/lib/google";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
|
||||
import { CredentialRepository } from "@calcom/lib/server/repository/credential";
|
||||
import { GoogleRepository } from "@calcom/lib/server/repository/google";
|
||||
import { Prisma } from "@calcom/prisma/client";
|
||||
|
||||
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
|
||||
import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState";
|
||||
import { REQUIRED_SCOPES, SCOPE_USERINFO_PROFILE } from "../lib/constants";
|
||||
import { getGoogleAppKeys } from "../lib/getGoogleAppKeys";
|
||||
|
||||
async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
@@ -40,14 +45,14 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
|
||||
const redirect_uri = `${WEBAPP_URL_FOR_OAUTH}/api/integrations/googlecalendar/callback`;
|
||||
|
||||
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uri);
|
||||
const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uri);
|
||||
|
||||
if (code) {
|
||||
const token = await oAuth2Client.getToken(code);
|
||||
const key = token.tokens;
|
||||
const grantedScopes = token.tokens.scope?.split(" ") ?? [];
|
||||
// Check if we have granted all required permissions
|
||||
const hasMissingRequiredScopes = REQUIRED_SCOPES.some((scope) => !grantedScopes.includes(scope));
|
||||
const hasMissingRequiredScopes = GOOGLE_CALENDAR_SCOPES.some((scope) => !grantedScopes.includes(scope));
|
||||
if (hasMissingRequiredScopes) {
|
||||
if (!state?.fromApp) {
|
||||
throw new HttpError({
|
||||
@@ -63,28 +68,25 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the primary calendar as the first selected calendar
|
||||
|
||||
oAuth2Client.setCredentials(key);
|
||||
|
||||
const calendar = google.calendar({
|
||||
version: "v3",
|
||||
const gcalCredential = await CredentialRepository.create({
|
||||
userId: req.session.user.id,
|
||||
key,
|
||||
appId: "google-calendar",
|
||||
type: "google_calendar",
|
||||
});
|
||||
|
||||
const gCalService = new GoogleCalendarService({
|
||||
...gcalCredential,
|
||||
user: null,
|
||||
});
|
||||
|
||||
const calendar = new calendar_v3.Calendar({
|
||||
auth: oAuth2Client,
|
||||
});
|
||||
|
||||
const cals = await getAllCalendars(calendar);
|
||||
|
||||
const primaryCal = cals.find((cal) => cal.primary) ?? cals[0];
|
||||
|
||||
// Only attempt to update the user's profile photo if the user has granted the required scope
|
||||
if (grantedScopes.includes(SCOPE_USERINFO_PROFILE)) {
|
||||
await updateProfilePhoto(oAuth2Client, req.session.user.id);
|
||||
}
|
||||
|
||||
const gcalCredential = await GoogleRepository.createGoogleCalendarCredential({
|
||||
key,
|
||||
userId: req.session.user.id,
|
||||
});
|
||||
const primaryCal = await gCalService.getPrimaryCalendar(calendar);
|
||||
|
||||
// If we still don't have a primary calendar skip creating the selected calendar.
|
||||
// It can be toggled on later.
|
||||
@@ -96,6 +98,11 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only attempt to update the user's profile photo if the user has granted the required scope
|
||||
if (grantedScopes.includes(SCOPE_USERINFO_PROFILE)) {
|
||||
await updateProfilePhotoGoogle(oAuth2Client, req.session.user.id);
|
||||
}
|
||||
|
||||
const selectedCalendarWhereUnique = {
|
||||
userId: req.session.user.id,
|
||||
externalId: primaryCal.id,
|
||||
@@ -105,10 +112,8 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Wrapping in a try/catch to reduce chance of race conditions-
|
||||
// also this improves performance for most of the happy-paths.
|
||||
try {
|
||||
await GoogleRepository.upsertSelectedCalendar({
|
||||
credentialId: gcalCredential.id,
|
||||
await gCalService.upsertSelectedCalendar({
|
||||
externalId: selectedCalendarWhereUnique.externalId,
|
||||
userId: selectedCalendarWhereUnique.userId,
|
||||
});
|
||||
} catch (error) {
|
||||
let errorMessage = "something_went_wrong";
|
||||
@@ -145,8 +150,9 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existingGoogleMeetCredential = await GoogleRepository.findGoogleMeetCredential({
|
||||
const existingGoogleMeetCredential = await CredentialRepository.findFirstByUserIdAndType({
|
||||
userId: req.session.user.id,
|
||||
type: "google_video",
|
||||
});
|
||||
|
||||
// If the user already has a google meet credential, there's nothing to do in here
|
||||
@@ -159,7 +165,12 @@ async function getHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
}
|
||||
|
||||
// Create a new google meet credential
|
||||
await GoogleRepository.createGoogleMeetsCredential({ userId: req.session.user.id });
|
||||
await CredentialRepository.create({
|
||||
userId: req.session.user.id,
|
||||
type: "google_video",
|
||||
key: {},
|
||||
appId: "google-meet",
|
||||
});
|
||||
res.redirect(
|
||||
getSafeRedirectUrl(`${WEBAPP_URL}/apps/installed/conferencing?hl=google-meet`) ??
|
||||
getInstalledAppPath({ variant: "conferencing", slug: "google-meet" })
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import prismock from "../../../../tests/libs/__mocks__/prisma";
|
||||
import oAuthManagerMock, { defaultMockOAuthManager } from "../../tests/__mocks__/OAuthManager";
|
||||
import { googleapisMock, setCredentialsMock } from "./__mocks__/googleapis";
|
||||
import { adminMock, calendarMock, setCredentialsMock } from "./__mocks__/googleapis";
|
||||
|
||||
import { expect, test, vi } from "vitest";
|
||||
import { expect, test, beforeEach, vi } from "vitest";
|
||||
import "vitest-fetch-mock";
|
||||
|
||||
import { CalendarCache } from "@calcom/features/calendar-cache/calendar-cache";
|
||||
@@ -22,7 +22,21 @@ vi.mock("./getGoogleAppKeys", () => ({
|
||||
redirect_uris: ["http://localhost:3000/api/integrations/googlecalendar/callback"],
|
||||
}),
|
||||
}));
|
||||
googleapisMock.google;
|
||||
|
||||
vi.mock("googleapis-common", () => ({
|
||||
OAuth2Client: vi.fn().mockImplementation(() => ({
|
||||
setCredentials: setCredentialsMock,
|
||||
})),
|
||||
}));
|
||||
vi.mock("@googleapis/admin", () => adminMock);
|
||||
vi.mock("@googleapis/calendar", () => calendarMock);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
setCredentialsMock.mockClear();
|
||||
calendarMock.calendar_v3.Calendar.mockClear();
|
||||
adminMock.admin_directory_v1.Admin.mockClear();
|
||||
});
|
||||
|
||||
const googleTestCredentialKey = {
|
||||
scope: "https://www.googleapis.com/auth/calendar.events",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { calendar_v3 } from "@googleapis/calendar";
|
||||
import type { Prisma } from "@prisma/client";
|
||||
import type { calendar_v3 } from "googleapis";
|
||||
import { google } from "googleapis";
|
||||
import { OAuth2Client } from "googleapis-common";
|
||||
import { RRule } from "rrule";
|
||||
import { v4 as uuid } from "uuid";
|
||||
|
||||
@@ -18,10 +18,9 @@ import {
|
||||
CREDENTIAL_SYNC_SECRET_HEADER_NAME,
|
||||
} from "@calcom/lib/constants";
|
||||
import { formatCalEvent } from "@calcom/lib/formatCalendarEvent";
|
||||
import { getAllCalendars } from "@calcom/lib/google";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { GoogleRepository } from "@calcom/lib/server/repository/google";
|
||||
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type {
|
||||
Calendar,
|
||||
@@ -164,8 +163,7 @@ export default class GoogleCalendarService implements Calendar {
|
||||
|
||||
public authedCalendar = async () => {
|
||||
const myGoogleAuth = await this.auth.getMyGoogleAuthWithRefreshedToken();
|
||||
const calendar = google.calendar({
|
||||
version: "v3",
|
||||
const calendar = new calendar_v3.Calendar({
|
||||
auth: myGoogleAuth,
|
||||
});
|
||||
return calendar;
|
||||
@@ -541,12 +539,12 @@ export default class GoogleCalendarService implements Calendar {
|
||||
// Only calendars of other integrations selected
|
||||
return [];
|
||||
}
|
||||
async function getCalIds() {
|
||||
const getCalIds = async () => {
|
||||
if (selectedCalendarIds.length !== 0) return selectedCalendarIds;
|
||||
const cals = await getAllCalendars(calendar, ["id"]);
|
||||
const cals = await this.getAllCalendars(calendar, ["id"]);
|
||||
if (!cals.length) return [];
|
||||
return cals.reduce((c, cal) => (cal.id ? [...c, cal.id] : c), [] as string[]);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const calsIds = await getCalIds();
|
||||
@@ -607,7 +605,7 @@ export default class GoogleCalendarService implements Calendar {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
data: {
|
||||
items: await getAllCalendars(calendar),
|
||||
items: await this.getAllCalendars(calendar),
|
||||
},
|
||||
})
|
||||
);
|
||||
@@ -652,10 +650,8 @@ export default class GoogleCalendarService implements Calendar {
|
||||
},
|
||||
});
|
||||
const response = res.data;
|
||||
await GoogleRepository.upsertSelectedCalendar({
|
||||
userId: this.credential.userId!,
|
||||
await this.upsertSelectedCalendar({
|
||||
externalId: calendarId,
|
||||
credentialId: this.credential.id,
|
||||
googleChannelId: response?.id,
|
||||
googleChannelKind: response?.kind,
|
||||
googleChannelResourceId: response?.resourceId,
|
||||
@@ -686,10 +682,8 @@ export default class GoogleCalendarService implements Calendar {
|
||||
.catch((err) => {
|
||||
console.warn(JSON.stringify(err));
|
||||
});
|
||||
await GoogleRepository.upsertSelectedCalendar({
|
||||
userId: this.credential.userId!,
|
||||
await this.upsertSelectedCalendar({
|
||||
externalId: calendarId,
|
||||
credentialId: this.credential.id,
|
||||
googleChannelId: null,
|
||||
googleChannelKind: null,
|
||||
googleChannelResourceId: null,
|
||||
@@ -715,9 +709,97 @@ export default class GoogleCalendarService implements Calendar {
|
||||
const data = await this.fetchAvailability(parsedArgs);
|
||||
await this.setAvailabilityInCache(parsedArgs, data);
|
||||
}
|
||||
|
||||
async createSelectedCalendar(
|
||||
data: Omit<Prisma.SelectedCalendarUncheckedCreateInput, "integration" | "credentialId">
|
||||
) {
|
||||
return await SelectedCalendarRepository.create({
|
||||
...data,
|
||||
integration: this.integrationName,
|
||||
credentialId: this.credential.id,
|
||||
});
|
||||
}
|
||||
|
||||
async upsertSelectedCalendar(
|
||||
data: Omit<Prisma.SelectedCalendarUncheckedCreateInput, "integration" | "credentialId" | "userId">
|
||||
) {
|
||||
if (!this.credential.userId) {
|
||||
logger.error("upsertSelectedCalendar failed. userId is missing.");
|
||||
return;
|
||||
}
|
||||
return await SelectedCalendarRepository.upsert({
|
||||
...data,
|
||||
integration: this.integrationName,
|
||||
credentialId: this.credential.id,
|
||||
userId: this.credential.userId,
|
||||
});
|
||||
}
|
||||
|
||||
async getAllCalendars(
|
||||
calendar: calendar_v3.Calendar,
|
||||
fields: string[] = ["id", "summary", "primary", "accessRole"]
|
||||
): Promise<calendar_v3.Schema$CalendarListEntry[]> {
|
||||
let allCalendars: calendar_v3.Schema$CalendarListEntry[] = [];
|
||||
let pageToken: string | undefined;
|
||||
|
||||
try {
|
||||
do {
|
||||
const response: any = await calendar.calendarList.list({
|
||||
fields: `items(${fields.join(",")}),nextPageToken`,
|
||||
pageToken,
|
||||
maxResults: 250, // 250 is max
|
||||
});
|
||||
|
||||
allCalendars = [...allCalendars, ...(response.data.items ?? [])];
|
||||
pageToken = response.data.nextPageToken;
|
||||
} while (pageToken);
|
||||
|
||||
return allCalendars;
|
||||
} catch (error) {
|
||||
logger.error("Error fetching all Google Calendars", { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getPrimaryCalendar(
|
||||
calendar: calendar_v3.Calendar,
|
||||
fields: string[] = ["id", "summary", "primary", "accessRole"]
|
||||
): Promise<calendar_v3.Schema$CalendarListEntry | null> {
|
||||
let pageToken: string | undefined;
|
||||
let firstCalendar: calendar_v3.Schema$CalendarListEntry | undefined;
|
||||
|
||||
try {
|
||||
do {
|
||||
const response: any = await calendar.calendarList.list({
|
||||
fields: `items(${fields.join(",")}),nextPageToken`,
|
||||
pageToken,
|
||||
maxResults: 250, // 250 is max
|
||||
});
|
||||
|
||||
const cals = response.data.items ?? [];
|
||||
const primaryCal = cals.find((cal: calendar_v3.Schema$CalendarListEntry) => cal.primary);
|
||||
if (primaryCal) {
|
||||
return primaryCal;
|
||||
}
|
||||
|
||||
// Store the first calendar in case no primary is found
|
||||
if (cals.length > 0 && !firstCalendar) {
|
||||
firstCalendar = cals[0];
|
||||
}
|
||||
|
||||
pageToken = response.data.nextPageToken;
|
||||
} while (pageToken);
|
||||
|
||||
// should not be reached because Google Cal always has a primary cal
|
||||
return firstCalendar ?? null;
|
||||
} catch (error) {
|
||||
logger.error("Error in `getPrimaryCalendar`", { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MyGoogleAuth extends google.auth.OAuth2 {
|
||||
class MyGoogleAuth extends OAuth2Client {
|
||||
constructor(client_id: string, client_secret: string, redirect_uri: string) {
|
||||
super(client_id, client_secret, redirect_uri);
|
||||
}
|
||||
|
||||
@@ -1,50 +1,33 @@
|
||||
import type * as googleapis from "googleapis";
|
||||
import { beforeEach, vi } from "vitest";
|
||||
import { mockDeep, mockReset } from "vitest-mock-extended";
|
||||
|
||||
vi.mock("googleapis", () => googleapisMock);
|
||||
|
||||
beforeEach(() => {
|
||||
mockReset(googleapisMock);
|
||||
});
|
||||
|
||||
const googleapisMock = mockDeep<typeof googleapis>({
|
||||
fallbackMockImplementation: (...args) => {
|
||||
console.log(args);
|
||||
throw new Error("Unimplemented");
|
||||
},
|
||||
});
|
||||
import { vi } from "vitest";
|
||||
|
||||
const setCredentialsMock = vi.fn();
|
||||
googleapisMock.google = {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
calendar: vi.fn().mockReturnValue({
|
||||
channels: {
|
||||
stop: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
events: {
|
||||
watch: vi.fn().mockResolvedValue({
|
||||
data: {
|
||||
kind: "api#channel",
|
||||
id: "mock-channel-id",
|
||||
resourceId: "mock-resource-id",
|
||||
resourceUri: "mock-resource-uri",
|
||||
expiration: "1111111111",
|
||||
},
|
||||
}),
|
||||
},
|
||||
}),
|
||||
auth: {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
OAuth2: function MockGoogleOAuth2() {
|
||||
return {
|
||||
setCredentials: setCredentialsMock,
|
||||
};
|
||||
},
|
||||
|
||||
const calendarMockImplementation = {
|
||||
channels: {
|
||||
stop: vi.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
events: {
|
||||
watch: vi.fn().mockResolvedValue({
|
||||
data: {
|
||||
kind: "api#channel",
|
||||
id: "mock-channel-id",
|
||||
resourceId: "mock-resource-id",
|
||||
resourceUri: "mock-resource-uri",
|
||||
expiration: "1111111111",
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
export default googleapisMock;
|
||||
export { googleapisMock, setCredentialsMock };
|
||||
const calendarMock = {
|
||||
calendar_v3: {
|
||||
Calendar: vi.fn().mockImplementation(() => calendarMockImplementation),
|
||||
},
|
||||
};
|
||||
const adminMock = {
|
||||
admin_directory_v1: {
|
||||
Admin: vi.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
export { calendarMock, adminMock, setCredentialsMock };
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export const SCOPE_USERINFO_PROFILE = "https://www.googleapis.com/auth/userinfo.profile";
|
||||
export const SCOPE_CALENDAR_READONLY = "https://www.googleapis.com/auth/calendar.readonly";
|
||||
export const SCOPE_CALENDAR_EVENT = "https://www.googleapis.com/auth/calendar.events";
|
||||
|
||||
export const REQUIRED_SCOPES = [SCOPE_CALENDAR_READONLY, SCOPE_CALENDAR_EVENT];
|
||||
|
||||
export const SCOPES = [...REQUIRED_SCOPES, SCOPE_USERINFO_PROFILE];
|
||||
@@ -1,29 +0,0 @@
|
||||
import type { calendar_v3 } from "googleapis";
|
||||
|
||||
import logger from "@calcom/lib/logger";
|
||||
|
||||
export async function getAllCalendars(
|
||||
calendar: calendar_v3.Calendar,
|
||||
fields: string[] = ["id", "summary", "primary", "accessRole"]
|
||||
): Promise<calendar_v3.Schema$CalendarListEntry[]> {
|
||||
let allCalendars: calendar_v3.Schema$CalendarListEntry[] = [];
|
||||
let pageToken: string | undefined;
|
||||
|
||||
try {
|
||||
do {
|
||||
const response: any = await calendar.calendarList.list({
|
||||
fields: `items(${fields.join(",")}),nextPageToken`,
|
||||
pageToken,
|
||||
maxResults: 250, // 250 is max
|
||||
});
|
||||
|
||||
allCalendars = [...allCalendars, ...(response.data.items ?? [])];
|
||||
pageToken = response.data.nextPageToken;
|
||||
} while (pageToken);
|
||||
|
||||
return allCalendars;
|
||||
} catch (error) {
|
||||
logger.error("Error fetching all Google Calendars", { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
"description": "Google Calendar is a time management and scheduling service developed by Google. Allows users to create and edit events, with options available for type and time. Available to anyone that has a Gmail account on both mobile and web versions.",
|
||||
"dependencies": {
|
||||
"@calcom/prisma": "*",
|
||||
"googleapis": "^84.0.0"
|
||||
"@googleapis/calendar": "^9.7.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/types": "*"
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
"main": "./index.ts",
|
||||
"description": "Google Meet is Google's web-based video conferencing platform, designed to compete with major conferencing platforms.",
|
||||
"dependencies": {
|
||||
"@calcom/prisma": "*",
|
||||
"googleapis": "^84.0.0"
|
||||
"@calcom/prisma": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/types": "*"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { calendar_v3 } from "@googleapis/calendar";
|
||||
import type { Membership, Team, UserPermissionRole } from "@prisma/client";
|
||||
import { waitUntil } from "@vercel/functions";
|
||||
import { OAuth2Client } from "googleapis-common";
|
||||
import type { AuthOptions, Session, User } from "next-auth";
|
||||
import type { JWT } from "next-auth/jwt";
|
||||
import { encode } from "next-auth/jwt";
|
||||
@@ -8,6 +10,8 @@ import CredentialsProvider from "next-auth/providers/credentials";
|
||||
import EmailProvider from "next-auth/providers/email";
|
||||
import GoogleProvider from "next-auth/providers/google";
|
||||
|
||||
import { updateProfilePhotoGoogle } from "@calcom/app-store/_utils/oauth/updateProfilePhotoGoogle";
|
||||
import GoogleCalendarService from "@calcom/app-store/googlecalendar/lib/CalendarService";
|
||||
import { LicenseKeySingleton } from "@calcom/ee/common/server/LicenseKeyService";
|
||||
import createUsersAndConnectToOrg from "@calcom/features/ee/dsync/lib/users/createUsersAndConnectToOrg";
|
||||
import postHogClient from "@calcom/features/ee/event-tracking/lib/posthog/postHogClient";
|
||||
@@ -15,7 +19,12 @@ import ImpersonationProvider from "@calcom/features/ee/impersonation/lib/Imperso
|
||||
import { getOrgFullOrigin, subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { clientSecretVerifier, hostedCal, isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
|
||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||
import { HOSTED_CAL_FEATURES, IS_CALCOM } from "@calcom/lib/constants";
|
||||
import {
|
||||
GOOGLE_CALENDAR_SCOPES,
|
||||
GOOGLE_OAUTH_SCOPES,
|
||||
HOSTED_CAL_FEATURES,
|
||||
IS_CALCOM,
|
||||
} from "@calcom/lib/constants";
|
||||
import { ENABLE_PROFILE_SWITCHER, IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { symmetricDecrypt, symmetricEncrypt } from "@calcom/lib/crypto";
|
||||
import { defaultCookies } from "@calcom/lib/default-cookies";
|
||||
@@ -23,6 +32,7 @@ import { isENVDev } from "@calcom/lib/env";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { randomString } from "@calcom/lib/random";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { CredentialRepository } from "@calcom/lib/server/repository/credential";
|
||||
import { ProfileRepository } from "@calcom/lib/server/repository/profile";
|
||||
import { UserRepository } from "@calcom/lib/server/repository/user";
|
||||
import slugify from "@calcom/lib/slugify";
|
||||
@@ -251,6 +261,13 @@ if (IS_GOOGLE_LOGIN_ENABLED) {
|
||||
clientId: GOOGLE_CLIENT_ID,
|
||||
clientSecret: GOOGLE_CLIENT_SECRET,
|
||||
allowDangerousEmailAccountLinking: true,
|
||||
authorization: {
|
||||
params: {
|
||||
scope: [...GOOGLE_OAUTH_SCOPES, ...GOOGLE_CALENDAR_SCOPES].join(" "),
|
||||
access_type: "offline",
|
||||
prompt: "consent",
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -608,6 +625,63 @@ export const getOptions = ({
|
||||
return await autoMergeIdentities();
|
||||
}
|
||||
|
||||
const grantedScopes = account.scope?.split(" ") ?? [];
|
||||
if (
|
||||
account.provider === "google" &&
|
||||
!(await CredentialRepository.findFirstByAppIdAndUserId({
|
||||
userId: user.id as number,
|
||||
appId: "google-calendar",
|
||||
})) &&
|
||||
GOOGLE_CALENDAR_SCOPES.every((scope) => grantedScopes.includes(scope))
|
||||
) {
|
||||
// Installing Google Calendar by default
|
||||
const credentialkey = {
|
||||
access_token: account.access_token,
|
||||
refresh_token: account.refresh_token,
|
||||
id_token: account.id_token,
|
||||
token_type: account.token_type,
|
||||
expires_at: account.expires_at,
|
||||
};
|
||||
const gcalCredential = await CredentialRepository.create({
|
||||
userId: user.id as number,
|
||||
key: credentialkey,
|
||||
appId: "google-calendar",
|
||||
type: "google_calendar",
|
||||
});
|
||||
const gCalService = new GoogleCalendarService({
|
||||
...gcalCredential,
|
||||
user: null,
|
||||
});
|
||||
|
||||
if (
|
||||
!(await CredentialRepository.findFirstByUserIdAndType({
|
||||
userId: user.id as number,
|
||||
type: "google_video",
|
||||
}))
|
||||
) {
|
||||
await CredentialRepository.create({
|
||||
type: "google_video",
|
||||
key: {},
|
||||
userId: user.id as number,
|
||||
appId: "google-meet",
|
||||
});
|
||||
}
|
||||
|
||||
const oAuth2Client = new OAuth2Client(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET);
|
||||
oAuth2Client.setCredentials(credentialkey);
|
||||
const calendar = new calendar_v3.Calendar({
|
||||
auth: oAuth2Client,
|
||||
});
|
||||
const primaryCal = await gCalService.getPrimaryCalendar(calendar);
|
||||
if (primaryCal?.id) {
|
||||
await gCalService.createSelectedCalendar({
|
||||
externalId: primaryCal.id,
|
||||
userId: user.id as number,
|
||||
});
|
||||
}
|
||||
await updateProfilePhotoGoogle(oAuth2Client, user.id as number);
|
||||
}
|
||||
|
||||
return {
|
||||
...token,
|
||||
id: existingUser.id,
|
||||
@@ -632,7 +706,6 @@ export const getOptions = ({
|
||||
log.debug("callbacks:session - Session callback called", safeStringify({ session, token, user }));
|
||||
const licenseKeyService = await LicenseKeySingleton.getInstance();
|
||||
const hasValidLicense = await licenseKeyService.checkLicense();
|
||||
|
||||
const profileId = token.profileId;
|
||||
const calendsoSession: Session = {
|
||||
...session,
|
||||
|
||||
@@ -189,3 +189,11 @@ export const RECORDING_DEFAULT_ICON = IS_PRODUCTION
|
||||
export const RECORDING_IN_PROGRESS_ICON = IS_PRODUCTION
|
||||
? `${WEBAPP_URL}/stop-recording.svg`
|
||||
: `https://app.cal.com/stop-recording.svg`;
|
||||
|
||||
export const SCOPE_USERINFO_PROFILE = "https://www.googleapis.com/auth/userinfo.profile";
|
||||
export const SCOPE_USERINFO_EMAIL = "https://www.googleapis.com/auth/userinfo.email";
|
||||
export const GOOGLE_OAUTH_SCOPES = [SCOPE_USERINFO_PROFILE, SCOPE_USERINFO_EMAIL];
|
||||
export const GOOGLE_CALENDAR_SCOPES = [
|
||||
"https://www.googleapis.com/auth/calendar.events",
|
||||
"https://www.googleapis.com/auth/calendar.readonly",
|
||||
];
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import type { OAuth2Client } from "google-auth-library";
|
||||
import type { calendar_v3 } from "googleapis";
|
||||
import { google } from "googleapis";
|
||||
|
||||
import logger from "@calcom/lib/logger";
|
||||
|
||||
import { UserRepository } from "./server/repository/user";
|
||||
|
||||
export async function getAllCalendars(
|
||||
calendar: calendar_v3.Calendar,
|
||||
fields: string[] = ["id", "summary", "primary", "accessRole"]
|
||||
): Promise<calendar_v3.Schema$CalendarListEntry[]> {
|
||||
let allCalendars: calendar_v3.Schema$CalendarListEntry[] = [];
|
||||
let pageToken: string | undefined;
|
||||
|
||||
try {
|
||||
do {
|
||||
const response: any = await calendar.calendarList.list({
|
||||
fields: `items(${fields.join(",")}),nextPageToken`,
|
||||
pageToken,
|
||||
maxResults: 250, // 250 is max
|
||||
});
|
||||
|
||||
allCalendars = [...allCalendars, ...(response.data.items ?? [])];
|
||||
pageToken = response.data.nextPageToken;
|
||||
} while (pageToken);
|
||||
|
||||
return allCalendars;
|
||||
} catch (error) {
|
||||
logger.error("Error fetching all Google Calendars", { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateProfilePhoto(oAuth2Client: OAuth2Client, userId: number) {
|
||||
try {
|
||||
const oauth2 = google.oauth2({ version: "v2", auth: oAuth2Client });
|
||||
const userDetails = await oauth2.userinfo.get();
|
||||
if (userDetails.data?.picture) {
|
||||
await UserRepository.updateAvatar({ id: userId, avatarUrl: userDetails.data.picture });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error updating avatarUrl from google calendar connect", error);
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,15 @@ export class CredentialRepository {
|
||||
});
|
||||
}
|
||||
|
||||
static async findFirstByAppIdAndUserId({ appId, userId }: { appId: string; userId: number }) {
|
||||
return await prisma.credential.findFirst({
|
||||
where: {
|
||||
appId,
|
||||
userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static async findFirstByUserIdAndType({ userId, type }: { userId: number; type: string }) {
|
||||
return await prisma.credential.findFirst({ where: { userId, type } });
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import type { Prisma } from "@prisma/client";
|
||||
import type { Credentials } from "google-auth-library";
|
||||
|
||||
import { CredentialRepository } from "./credential";
|
||||
import { SelectedCalendarRepository } from "./selectedCalendar";
|
||||
|
||||
export class GoogleRepository {
|
||||
static async createGoogleCalendarCredential({ userId, key }: { userId: number; key: Credentials }) {
|
||||
return await CredentialRepository.create({
|
||||
type: "google_calendar",
|
||||
key,
|
||||
userId,
|
||||
appId: "google-calendar",
|
||||
});
|
||||
}
|
||||
|
||||
static async createGoogleMeetsCredential({ userId }: { userId: number }) {
|
||||
return await CredentialRepository.create({
|
||||
type: "google_video",
|
||||
key: {},
|
||||
userId,
|
||||
appId: "google-meet",
|
||||
});
|
||||
}
|
||||
|
||||
static async createSelectedCalendar(data: { credentialId: number; userId: number; externalId: string }) {
|
||||
return await SelectedCalendarRepository.create({
|
||||
...data,
|
||||
integration: "google_calendar",
|
||||
});
|
||||
}
|
||||
|
||||
static async upsertSelectedCalendar(
|
||||
data: Omit<Prisma.SelectedCalendarUncheckedCreateInput, "integration">
|
||||
) {
|
||||
return await SelectedCalendarRepository.upsert({
|
||||
...data,
|
||||
integration: "google_calendar",
|
||||
});
|
||||
}
|
||||
|
||||
static async findGoogleMeetCredential({ userId }: { userId: number }) {
|
||||
return await CredentialRepository.findFirstByUserIdAndType({
|
||||
userId,
|
||||
type: "google_video",
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,8 @@ import type { Prisma } from "@prisma/client";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { credentialForCalendarServiceSelect } from "@calcom/prisma/selects/credential";
|
||||
|
||||
type SelectedCalendarCreateInput = {
|
||||
credentialId: number;
|
||||
userId: number;
|
||||
externalId: string;
|
||||
integration: string;
|
||||
};
|
||||
|
||||
export class SelectedCalendarRepository {
|
||||
static async create(data: SelectedCalendarCreateInput) {
|
||||
static async create(data: Prisma.SelectedCalendarUncheckedCreateInput) {
|
||||
return await prisma.selectedCalendar.create({
|
||||
data: {
|
||||
...data,
|
||||
|
||||
@@ -54,7 +54,7 @@ export default defineConfig({
|
||||
"ical.js",
|
||||
"ics",
|
||||
"tsdav",
|
||||
"googleapis",
|
||||
"@googleapis/calendar",
|
||||
"rrule",
|
||||
"@hubspot/api-client",
|
||||
"querystring",
|
||||
@@ -125,7 +125,7 @@ export default defineConfig({
|
||||
"ical.js": "ical.js",
|
||||
ics: "ics",
|
||||
tsdav: "tsdav",
|
||||
googleapis: "googleapis",
|
||||
"@googleapis/calendar": "@googleapis/calendar",
|
||||
rrule: "rrule",
|
||||
"@hubspot/api-client": "@hubspot/api-client",
|
||||
querystring: "querystring",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { google } from "googleapis";
|
||||
import { admin_directory_v1 } from "@googleapis/admin";
|
||||
import { OAuth2Client } from "googleapis-common";
|
||||
import { z } from "zod";
|
||||
|
||||
import getAppKeysFromSlug from "@calcom/app-store/_utils/getAppKeysFromSlug";
|
||||
@@ -48,14 +49,15 @@ export const getUsersFromGWorkspace = async ({}: CheckForGCalOptions) => {
|
||||
|
||||
const credentials = credentialsSchema.parse(hasExistingCredentials.key);
|
||||
|
||||
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret);
|
||||
const oAuth2Client = new OAuth2Client(client_id, client_secret);
|
||||
|
||||
// Set users credentials instead of our app credentials - allowing us to make requests on their behalf
|
||||
oAuth2Client.setCredentials(credentials);
|
||||
|
||||
// Create a new instance of the Admin SDK directory API
|
||||
const directory = google.admin({ version: "directory_v1", auth: oAuth2Client });
|
||||
|
||||
const directory = new admin_directory_v1.Admin({
|
||||
auth: oAuth2Client as any,
|
||||
});
|
||||
const { data } = await directory.users.list({
|
||||
maxResults: 200, // Up this if we ever need to get more than 200 users
|
||||
customer: "my_customer", // This only works for single domain setups - we'll need to change this if we ever support multi-domain setups (unlikely we'll ever need to)
|
||||
|
||||
Vendored
+1
-1
@@ -5,7 +5,7 @@ import type {
|
||||
SelectedCalendar as _SelectedCalendar,
|
||||
} from "@prisma/client";
|
||||
import type { Dayjs } from "dayjs";
|
||||
import type { calendar_v3 } from "googleapis";
|
||||
import type { calendar_v3 } from "@googleapis/calendar";
|
||||
import type { Time } from "ical.js";
|
||||
import type { TFunction } from "next-i18next";
|
||||
import type z from "zod";
|
||||
|
||||
@@ -4195,7 +4195,7 @@ __metadata:
|
||||
dependencies:
|
||||
"@calcom/prisma": "*"
|
||||
"@calcom/types": "*"
|
||||
googleapis: ^84.0.0
|
||||
"@googleapis/calendar": ^9.7.9
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -4205,7 +4205,6 @@ __metadata:
|
||||
dependencies:
|
||||
"@calcom/prisma": "*"
|
||||
"@calcom/types": "*"
|
||||
googleapis: ^84.0.0
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -5016,6 +5015,9 @@ __metadata:
|
||||
"@dub/analytics": ^0.0.15
|
||||
"@formkit/auto-animate": 1.0.0-beta.5
|
||||
"@glidejs/glide": ^3.5.2
|
||||
"@googleapis/admin": ^23.0.0
|
||||
"@googleapis/calendar": ^9.7.9
|
||||
"@googleapis/oauth2": ^1.0.7
|
||||
"@hookform/error-message": ^2.0.0
|
||||
"@hookform/resolvers": ^2.9.7
|
||||
"@microsoft/microsoft-graph-types-beta": 0.15.0-preview
|
||||
@@ -5084,7 +5086,7 @@ __metadata:
|
||||
entities: ^4.4.0
|
||||
env-cmd: ^10.1.0
|
||||
eslint-config-next: ^13.2.1
|
||||
googleapis: ^84.0.0
|
||||
google-auth-library: ^9.15.0
|
||||
gray-matter: ^4.0.3
|
||||
handlebars: ^4.7.7
|
||||
ical.js: ^1.4.0
|
||||
@@ -6593,6 +6595,33 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@googleapis/admin@npm:^23.0.0":
|
||||
version: 23.0.0
|
||||
resolution: "@googleapis/admin@npm:23.0.0"
|
||||
dependencies:
|
||||
googleapis-common: ^7.0.0
|
||||
checksum: 385ec58507646c4ccbee7309cfbfbe3c6dcb8879ed8b8510d95b3877f2b4eb5340472e68dae8402cf90415cc636b9f64c3353faf63ec8d6ac88388a53a26290c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@googleapis/calendar@npm:^9.7.9":
|
||||
version: 9.7.9
|
||||
resolution: "@googleapis/calendar@npm:9.7.9"
|
||||
dependencies:
|
||||
googleapis-common: ^7.0.0
|
||||
checksum: f767e1d897b038a9ebada71dcb4fc3564813769299025877fb574c0716cb2cebfc4b5d88f8fbcfbc9ccc73c3eb7448ac0c20ceb94df4239dc0bd0f902657db9f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@googleapis/oauth2@npm:^1.0.7":
|
||||
version: 1.0.7
|
||||
resolution: "@googleapis/oauth2@npm:1.0.7"
|
||||
dependencies:
|
||||
googleapis-common: ^7.0.0
|
||||
checksum: e99227439409aff14f228d4cfb5316e284072fe51ab7b65e16c02d0b90e7e9c67cbc1c4d7a502aff7bf992410478fc5c29f06ad4f180e7d8923690e9c344f6de
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grpc/grpc-js@npm:^1.7.1":
|
||||
version: 1.9.14
|
||||
resolution: "@grpc/grpc-js@npm:1.9.14"
|
||||
@@ -25579,9 +25608,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"fast-text-encoding@npm:^1.0.0":
|
||||
version: 1.0.3
|
||||
resolution: "fast-text-encoding@npm:1.0.3"
|
||||
checksum: 3e51365896f06d0dcab128092d095a0037d274deec419fecbd2388bc236d7b387610e0c72f920c6126e00c885ab096fbfaa3645712f5b98f721bef6b064916a8
|
||||
version: 1.0.6
|
||||
resolution: "fast-text-encoding@npm:1.0.6"
|
||||
checksum: 9d58f694314b3283e785bf61954902536da228607ad246905e30256f9ab8331f780ac987e7222c9f5eafd04168d07e12b8054c85cedb76a2c05af0e82387a903
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -26593,15 +26622,15 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"gaxios@npm:^4.0.0":
|
||||
version: 4.3.2
|
||||
resolution: "gaxios@npm:4.3.2"
|
||||
version: 4.3.3
|
||||
resolution: "gaxios@npm:4.3.3"
|
||||
dependencies:
|
||||
abort-controller: ^3.0.0
|
||||
extend: ^3.0.2
|
||||
https-proxy-agent: ^5.0.0
|
||||
is-stream: ^2.0.0
|
||||
node-fetch: ^2.6.1
|
||||
checksum: 1305fc6a4b9f888a7424ed160084d1f253a54bec04b4986ecb7929d8aa52382e737fc65feb03f0b0b47c524575286d765806d829868ec49fd383a1be8a973870
|
||||
node-fetch: ^2.6.7
|
||||
checksum: 0b72a00875404e2c3d7aca9f32535e931d7b0ebb850dc92fafc1685b99a109b04205c63e4637a2d0d9a261ac50adf83f7d33435f73e256dcca32564ef9358fee
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -27216,14 +27245,28 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"google-p12-pem@npm:^3.1.3":
|
||||
version: 3.1.3
|
||||
resolution: "google-p12-pem@npm:3.1.3"
|
||||
"google-auth-library@npm:^9.15.0":
|
||||
version: 9.15.0
|
||||
resolution: "google-auth-library@npm:9.15.0"
|
||||
dependencies:
|
||||
node-forge: ^1.0.0
|
||||
base64-js: ^1.3.0
|
||||
ecdsa-sig-formatter: ^1.0.11
|
||||
gaxios: ^6.1.1
|
||||
gcp-metadata: ^6.1.0
|
||||
gtoken: ^7.0.0
|
||||
jws: ^4.0.0
|
||||
checksum: 7ff63ff755ce43601a1780cfbe6d8b523792bfd2b653a27180e41735b9f0ba5689668eb8b079c915300041e412ec00b7635b5c26e0f2139fecacb346b6f9f12f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"google-p12-pem@npm:^3.1.3":
|
||||
version: 3.1.4
|
||||
resolution: "google-p12-pem@npm:3.1.4"
|
||||
dependencies:
|
||||
node-forge: ^1.3.1
|
||||
bin:
|
||||
gp12-pem: build/src/bin/gp12-pem.js
|
||||
checksum: 8628f2bf9b4c9b3bfc7220906c15af2b306e2ef30c7c398327a9cff9d4a12285f104545f00c46b716dd23966615f446d7e5efde44d590f9483d345be78ea115e
|
||||
checksum: 72ce13b9536c69f21584cb7477ed4c34674325639a5dac42e8d774b78d367cb196ae7d37a52bba868235205760df353ac678a5e1756a4f4ded82e16d29d6cbb1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -33871,7 +33914,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-forge@npm:1.3.1, node-forge@npm:^1.0.0":
|
||||
"node-forge@npm:1.3.1, node-forge@npm:^1.3.1":
|
||||
version: 1.3.1
|
||||
resolution: "node-forge@npm:1.3.1"
|
||||
checksum: 08fb072d3d670599c89a1704b3e9c649ff1b998256737f0e06fbd1a5bf41cae4457ccaee32d95052d80bbafd9ffe01284e078c8071f0267dc9744e51c5ed42a9
|
||||
|
||||
Reference in New Issue
Block a user