diff --git a/apps/web/test/utils/bookingScenario/bookingScenario.ts b/apps/web/test/utils/bookingScenario/bookingScenario.ts index 13008b8301..396642618d 100644 --- a/apps/web/test/utils/bookingScenario/bookingScenario.ts +++ b/apps/web/test/utils/bookingScenario/bookingScenario.ts @@ -51,6 +51,22 @@ vi.mock("@calcom/app-store/calendar.services.generated", () => ({ }, })); +const mockVideoAdapterRegistry: Record = {}; + +vi.mock("@calcom/app-store/video.adapters.generated", () => ({ + VideoApiAdapterMap: new Proxy( + {}, + { + get(target, prop) { + if (typeof prop === "string" && mockVideoAdapterRegistry[prop]) { + return mockVideoAdapterRegistry[prop]; + } + return Promise.resolve({ default: vi.fn() }); + }, + } + ), +})); + // We don't need to test it. Also, it causes Formbricks error when imported vi.mock("@calcom/lib/raqb/findTeamMembersMatchingAttributeLogic", () => ({ default: {}, @@ -1995,6 +2011,61 @@ export function mockVideoApp({ const updateMeetingCalls: any[] = []; // eslint-disable-next-line @typescript-eslint/no-explicit-any const deleteMeetingCalls: any[] = []; + + const mockVideoAdapter = (credential: any) => { + return { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + createMeeting: (...rest: any[]) => { + if (creationCrash) { + throw new Error("MockVideoApiAdapter.createMeeting fake error"); + } + createMeetingCalls.push({ + credential, + args: rest, + }); + + return Promise.resolve({ + type: appStoreMetadata[metadataLookupKey as keyof typeof appStoreMetadata].type, + ...videoMeetingData, + }); + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + updateMeeting: async (...rest: any[]) => { + if (updationCrash) { + throw new Error("MockVideoApiAdapter.updateMeeting fake error"); + } + const [bookingRef, calEvent] = rest; + updateMeetingCalls.push({ + credential, + args: rest, + }); + if (!bookingRef.type) { + throw new Error("bookingRef.type is not defined"); + } + if (!calEvent.organizer) { + throw new Error("calEvent.organizer is not defined"); + } + log.silly("MockVideoApiAdapter.updateMeeting", JSON.stringify({ bookingRef, calEvent })); + return Promise.resolve({ + type: appStoreMetadata[metadataLookupKey as keyof typeof appStoreMetadata].type, + ...videoMeetingData, + }); + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + deleteMeeting: async (...rest: any[]) => { + log.silly("MockVideoApiAdapter.deleteMeeting", JSON.stringify(rest)); + deleteMeetingCalls.push({ + credential, + args: rest, + }); + }, + }; + }; + + mockVideoAdapterRegistry[appStoreLookupKey] = Promise.resolve({ + default: mockVideoAdapter, + }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore appStoreMock.default[appStoreLookupKey as keyof typeof appStoreMock.default].mockImplementation(() => { @@ -2003,59 +2074,12 @@ export function mockVideoApp({ lib: { // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore - VideoApiAdapter: (credential) => { - return { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - createMeeting: (...rest: any[]) => { - if (creationCrash) { - throw new Error("MockVideoApiAdapter.createMeeting fake error"); - } - createMeetingCalls.push({ - credential, - args: rest, - }); - - return Promise.resolve({ - type: appStoreMetadata[metadataLookupKey as keyof typeof appStoreMetadata].type, - ...videoMeetingData, - }); - }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - updateMeeting: async (...rest: any[]) => { - if (updationCrash) { - throw new Error("MockVideoApiAdapter.updateMeeting fake error"); - } - const [bookingRef, calEvent] = rest; - updateMeetingCalls.push({ - credential, - args: rest, - }); - if (!bookingRef.type) { - throw new Error("bookingRef.type is not defined"); - } - if (!calEvent.organizer) { - throw new Error("calEvent.organizer is not defined"); - } - log.silly("MockVideoApiAdapter.updateMeeting", JSON.stringify({ bookingRef, calEvent })); - return Promise.resolve({ - type: appStoreMetadata[metadataLookupKey as keyof typeof appStoreMetadata].type, - ...videoMeetingData, - }); - }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - deleteMeeting: async (...rest: any[]) => { - log.silly("MockVideoApiAdapter.deleteMeeting", JSON.stringify(rest)); - deleteMeetingCalls.push({ - credential, - args: rest, - }); - }, - }; - }, + VideoApiAdapter: mockVideoAdapter, }, }); }); }); + return { createMeetingCalls, updateMeetingCalls, @@ -2121,6 +2145,17 @@ export function mockErrorOnVideoMeetingCreation({ appStoreLookupKey?: string; }) { appStoreLookupKey = appStoreLookupKey || metadataLookupKey; + + const mockErrorAdapter = () => ({ + createMeeting: () => { + throw new MockError("Error creating Video meeting"); + }, + }); + + mockVideoAdapterRegistry[appStoreLookupKey] = Promise.resolve({ + default: mockErrorAdapter, + }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore appStoreMock.default[appStoreLookupKey].mockImplementation(() => { @@ -2129,11 +2164,7 @@ export function mockErrorOnVideoMeetingCreation({ lib: { // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore - VideoApiAdapter: () => ({ - createMeeting: () => { - throw new MockError("Error creating Video meeting"); - }, - }), + VideoApiAdapter: mockErrorAdapter, }, }); }); diff --git a/packages/app-store-cli/src/build.ts b/packages/app-store-cli/src/build.ts index f6b8b988e1..e52cc73439 100644 --- a/packages/app-store-cli/src/build.ts +++ b/packages/app-store-cli/src/build.ts @@ -433,6 +433,40 @@ function generateFiles() { paymentOutput.push(...paymentServices); + const videoOutput = []; + const videoAdapters = getExportedObject( + "VideoApiAdapterMap", + { + importConfig: { + fileToBeImported: "lib/VideoApiAdapter.ts", + importName: "default", + }, + lazyImport: true, + }, + (app: App) => { + return fs.existsSync(path.join(APP_STORE_PATH, app.path, "lib/VideoApiAdapter.ts")); + } + ); + + const videoExportLineIndex = videoAdapters.findIndex((line) => + line.startsWith("export const VideoApiAdapterMap") + ); + if (videoExportLineIndex !== -1) { + const exportLine = videoAdapters[videoExportLineIndex]; + const objectContent = videoAdapters.slice(videoExportLineIndex + 1, -1); + + videoOutput.push( + exportLine.replace( + "export const VideoApiAdapterMap = {", + "export const VideoApiAdapterMap = process.env.NEXT_PUBLIC_IS_E2E ? {} : {" + ), + ...objectContent, + "};" + ); + } else { + videoOutput.push(...videoAdapters); + } + const banner = `/** This file is autogenerated using the command \`yarn app-store:build --watch\`. Don't modify this file manually. @@ -449,6 +483,7 @@ function generateFiles() { ["crm.apps.generated.ts", crmOutput], ["calendar.services.generated.ts", calendarOutput], ["payment.services.generated.ts", paymentOutput], + ["video.adapters.generated.ts", videoOutput], ]; filesToGenerate.forEach(([fileName, output]) => { fs.writeFileSync(`${APP_STORE_PATH}/${fileName}`, formatOutput(`${banner}${output.join("\n")}`)); diff --git a/packages/app-store/video.adapters.generated.ts b/packages/app-store/video.adapters.generated.ts new file mode 100644 index 0000000000..693e69168e --- /dev/null +++ b/packages/app-store/video.adapters.generated.ts @@ -0,0 +1,19 @@ +/** + This file is autogenerated using the command `yarn app-store:build --watch`. + Don't modify this file manually. +**/ +export const VideoApiAdapterMap = process.env.NEXT_PUBLIC_IS_E2E + ? {} + : { + dailyvideo: import("./dailyvideo/lib/VideoApiAdapter"), + huddle01video: import("./huddle01video/lib/VideoApiAdapter"), + jelly: import("./jelly/lib/VideoApiAdapter"), + jitsivideo: import("./jitsivideo/lib/VideoApiAdapter"), + nextcloudtalk: import("./nextcloudtalk/lib/VideoApiAdapter"), + office365video: import("./office365video/lib/VideoApiAdapter"), + shimmervideo: import("./shimmervideo/lib/VideoApiAdapter"), + sylapsvideo: import("./sylapsvideo/lib/VideoApiAdapter"), + tandemvideo: import("./tandemvideo/lib/VideoApiAdapter"), + webex: import("./webex/lib/VideoApiAdapter"), + zoomvideo: import("./zoomvideo/lib/VideoApiAdapter"), + }; diff --git a/packages/lib/videoClient.ts b/packages/lib/videoClient.ts index 3c474e4311..4025e0d0a9 100644 --- a/packages/lib/videoClient.ts +++ b/packages/lib/videoClient.ts @@ -3,6 +3,7 @@ import { v5 as uuidv5 } from "uuid"; import { getDailyAppKeys } from "@calcom/app-store/dailyvideo/lib/getDailyAppKeys"; import { DailyLocationType } from "@calcom/app-store/locations"; +import { VideoApiAdapterMap } from "@calcom/app-store/video.adapters.generated"; import { sendBrokenIntegrationEmail } from "@calcom/emails"; import { getUid } from "@calcom/lib/CalEventParser"; import logger from "@calcom/lib/logger"; @@ -27,23 +28,21 @@ const getVideoAdapters = async (withCredentials: CredentialPayload[]): Promise m.default); - const appImportFn = appStore[appName as keyof typeof appStore]; + const videoAdapterImport = VideoApiAdapterMap[appName as keyof typeof VideoApiAdapterMap]; - // Static Link Video Apps don't exist in packages/app-store/index.ts(it's manually maintained at the moment) and they aren't needed there anyway. - const app = appImportFn ? await appImportFn() : null; - - if (!app) { + if (!videoAdapterImport) { log.error(`Couldn't get adapter for ${appName}`); continue; } - if ("lib" in app && "VideoApiAdapter" in app.lib) { - const makeVideoApiAdapter = app.lib.VideoApiAdapter as VideoApiAdapterFactory; + const videoAdapterModule = await videoAdapterImport; + const makeVideoApiAdapter = videoAdapterModule.default as VideoApiAdapterFactory; + + if (makeVideoApiAdapter) { const videoAdapter = makeVideoApiAdapter(cred); videoAdapters.push(videoAdapter); } else { - log.error(`App ${appName} doesn't have 'lib.VideoApiAdapter' defined`); + log.error(`App ${appName} doesn't have a default VideoApiAdapter export`); } } diff --git a/packages/trpc/server/routers/viewer/calVideo/getMeetingInformation.handler.ts b/packages/trpc/server/routers/viewer/calVideo/getMeetingInformation.handler.ts index de12ba635b..9a70196ba5 100644 --- a/packages/trpc/server/routers/viewer/calVideo/getMeetingInformation.handler.ts +++ b/packages/trpc/server/routers/viewer/calVideo/getMeetingInformation.handler.ts @@ -1,4 +1,4 @@ -import DailyVideoApiAdapter from "@calcom/app-store/dailyvideo/lib/VideoApiAdapter"; +import { VideoApiAdapterMap } from "@calcom/app-store/video.adapters.generated"; import type { TrpcSessionUser } from "@calcom/trpc/server/types"; import { TRPCError } from "@trpc/server"; @@ -16,7 +16,17 @@ export const getMeetingInformationHandler = async ({ ctx: _ctx, input }: GetMeet const { roomName } = input; try { - const videoApiAdapter = DailyVideoApiAdapter(); + const dailyVideoAdapterImport = VideoApiAdapterMap["dailyvideo"]; + if (!dailyVideoAdapterImport) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Daily video adapter not available", + }); + } + + const dailyVideoAdapterModule = await dailyVideoAdapterImport; + const videoApiAdapter = dailyVideoAdapterModule.default(); + if (!videoApiAdapter || !videoApiAdapter.getMeetingInformation) { throw new TRPCError({ code: "BAD_REQUEST",