Files
calendar/packages/features/conferencing/lib/videoClient.ts
T
MorganGitHubmorgan@cal.com <morgan@cal.com>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>hbjORbj
a73b804d48 refactor: Split EmailManager into focused service files (#24997)
* refactor: Split EmailManager into focused service files

- Created separate service files for different email categories:
  - auth-email-service.ts: Authentication and verification emails
  - organization-email-service.ts: Organization and team emails
  - billing-email-service.ts: Payment and credit-related emails
  - integration-email-service.ts: Integration and app-related emails
  - workflow-email-service.ts: Workflow and custom emails
  - recording-email-service.ts: Recording and transcript emails

- Refactored email-manager.ts to keep only core booking lifecycle functions
- Removed unused imports from email-manager.ts
- Updated index.ts to export from all new service files
- Updated all imports across the codebase to use package root (@calcom/emails)
- Fixed lint warnings in handleChildrenEventTypes.ts

This reduces the import cost of EmailManager by allowing consumers to import only the specific email services they need.

Co-Authored-By: morgan@cal.com <morgan@cal.com>

* refactor: Update all imports to use direct service file paths

- Update 49 files to import directly from service files instead of barrel file
- Update packages/emails/index.ts to keep only email-manager and renderEmail exports
- Fix dynamic import in passwordResetRequest.ts
- Update renderEmail imports to use direct path
- Update test file to import from specific service module
- Fix ESLint warnings in modified files (unused variables, unused expressions)

This ensures consumers only import the specific email services they need,
reducing import cost by avoiding the barrel file pattern for service files.

Co-Authored-By: morgan@cal.com <morgan@cal.com>

* fix: Use default import for renderEmail

renderEmail is exported as a default export, not a named export.
Changed from 'import { renderEmail }' to 'import renderEmail'.

Co-Authored-By: morgan@cal.com <morgan@cal.com>

* fix: Update test mocks to use direct service file imports

- Update handleNoShowFee.test.ts to mock @calcom/emails/billing-email-service
- Update credit-service.test.ts to mock @calcom/emails/billing-email-service
- These tests were failing because they were mocking the barrel file @calcom/emails
  which no longer exports service functions after the refactoring

Co-Authored-By: morgan@cal.com <morgan@cal.com>

* fix: unit test spy

* fix: unit test mock

* address cubic comments

* fix: type error sendMonthlyDigestEmail

* remove barrel file and sendEmail unused task

* fixup! remove barrel file and sendEmail unused task

* fixup! fixup! remove barrel file and sendEmail unused task

* fix: integration test mock emails

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: hbjORbj <sldisek783@gmail.com>
2025-11-11 14:21:10 +02:00

405 lines
12 KiB
TypeScript

import short from "short-uuid";
import { v5 as uuidv5 } from "uuid";
import { DailyLocationType } from "@calcom/app-store/constants";
import { getDailyAppKeys } from "@calcom/app-store/dailyvideo/lib/getDailyAppKeys";
import { getVideoAdapters } from "@calcom/app-store/getVideoAdapters";
import { sendBrokenIntegrationEmail } from "@calcom/emails/integration-email-service";
import { getUid } from "@calcom/lib/CalEventParser";
import logger from "@calcom/lib/logger";
import { getPiiFreeCalendarEvent, getPiiFreeCredential } from "@calcom/lib/piiFreeData";
import { safeStringify } from "@calcom/lib/safeStringify";
import { prisma } from "@calcom/prisma";
import type { GetRecordingsResponseSchema, GetAccessLinkResponseSchema } from "@calcom/prisma/zod-utils";
import type { CalendarEvent, EventBusyDate } from "@calcom/types/Calendar";
import type { CredentialPayload } from "@calcom/types/Credential";
import type { EventResult, PartialReference } from "@calcom/types/EventManager";
import type { VideoCallData } from "@calcom/types/VideoApiAdapter";
const log = logger.getSubLogger({ prefix: ["[features/conferencing/lib] videoClient"] });
const translator = short();
const getBusyVideoTimes = async (withCredentials: CredentialPayload[]) =>
Promise.all((await getVideoAdapters(withCredentials)).map((c) => c?.getAvailability())).then((results) =>
results.reduce((acc, availability) => acc.concat(availability), [] as (EventBusyDate | undefined)[])
);
const createMeeting = async (credential: CredentialPayload, calEvent: CalendarEvent) => {
const uid: string = getUid(calEvent);
log.debug(
"createMeeting",
safeStringify({
credential: getPiiFreeCredential(credential),
uid,
calEvent: getPiiFreeCalendarEvent(calEvent),
})
);
if (!credential || !credential.appId) {
throw new Error(
"Credentials must be set! Video platforms are optional, so this method shouldn't even be called when no video credentials are set."
);
}
const videoAdapters = await getVideoAdapters([credential]);
const [firstVideoAdapter] = videoAdapters;
let createdMeeting;
let returnObject: {
appName: string;
type: string;
uid: string;
originalEvent: CalendarEvent;
success: boolean;
createdEvent: VideoCallData | undefined;
credentialId: number;
} = {
appName: credential.appId || "",
type: credential.type,
uid,
originalEvent: calEvent,
success: false,
createdEvent: undefined,
credentialId: credential.id,
};
try {
// Check to see if video app is enabled
const enabledApp = await prisma.app.findUnique({
where: {
slug: credential.appId,
},
select: {
enabled: true,
},
});
if (!enabledApp?.enabled)
throw `Location app ${credential.appId} is either disabled or not seeded at all`;
createdMeeting = await firstVideoAdapter?.createMeeting(calEvent);
returnObject = { ...returnObject, createdEvent: createdMeeting, success: true };
log.debug("created Meeting", safeStringify(returnObject));
} catch (err) {
await sendBrokenIntegrationEmail(calEvent, "video");
log.error(
"createMeeting failed",
safeStringify(err),
safeStringify({ calEvent: getPiiFreeCalendarEvent(calEvent) })
);
// Default to calVideo
const defaultMeeting = await createMeetingWithCalVideo(calEvent);
if (defaultMeeting) {
calEvent.location = DailyLocationType;
}
returnObject = { ...returnObject, originalEvent: calEvent, createdEvent: defaultMeeting };
}
return returnObject;
};
const updateMeeting = async (
credential: CredentialPayload,
calEvent: CalendarEvent,
bookingRef: PartialReference | null
): Promise<EventResult<VideoCallData>> => {
const uid = translator.fromUUID(uuidv5(JSON.stringify(calEvent), uuidv5.URL));
let success = true;
const [firstVideoAdapter] = await getVideoAdapters([credential]);
const canCallUpdateMeeting = !!(credential && bookingRef);
const updatedMeeting = canCallUpdateMeeting
? await firstVideoAdapter?.updateMeeting(bookingRef, calEvent).catch(async (e) => {
await sendBrokenIntegrationEmail(calEvent, "video");
log.error("updateMeeting failed", e, calEvent);
success = false;
return undefined;
})
: undefined;
if (!updatedMeeting) {
log.error(
"updateMeeting failed",
safeStringify({ bookingRef, canCallUpdateMeeting, calEvent, credential })
);
return {
appName: credential.appId || "",
type: credential.type,
success,
uid,
originalEvent: calEvent,
};
}
return {
appName: credential.appId || "",
type: credential.type,
success,
uid,
updatedEvent: updatedMeeting,
originalEvent: calEvent,
};
};
const deleteMeeting = async (credential: CredentialPayload | null, uid: string): Promise<unknown> => {
if (credential) {
const videoAdapter = (await getVideoAdapters([credential]))[0];
log.debug(
"Calling deleteMeeting for",
safeStringify({ credential: getPiiFreeCredential(credential), uid })
);
// There are certain video apps with no video adapter defined. e.g. riverby,whereby
if (videoAdapter) {
return videoAdapter.deleteMeeting(uid);
}
}
return Promise.resolve({});
};
// @TODO: This is a temporary solution to create a meeting with cal.com video as fallback url
const createMeetingWithCalVideo = async (calEvent: CalendarEvent) => {
let dailyAppKeys: Awaited<ReturnType<typeof getDailyAppKeys>>;
try {
dailyAppKeys = await getDailyAppKeys();
} catch {
return;
}
const [videoAdapter] = await getVideoAdapters([
{
id: 0,
appId: "daily-video",
type: "daily_video",
userId: null,
user: { email: "" },
teamId: null,
key: dailyAppKeys,
invalid: false,
delegationCredentialId: null,
},
]);
return videoAdapter?.createMeeting(calEvent);
};
export const createInstantMeetingWithCalVideo = async (endTime: string) => {
let dailyAppKeys: Awaited<ReturnType<typeof getDailyAppKeys>>;
try {
dailyAppKeys = await getDailyAppKeys();
} catch {
return;
}
const [videoAdapter] = await getVideoAdapters([
{
id: 0,
appId: "daily-video",
type: "daily_video",
userId: null,
user: { email: "" },
teamId: null,
key: dailyAppKeys,
invalid: false,
delegationCredentialId: null,
},
]);
return videoAdapter?.createInstantCalVideoRoom?.(endTime);
};
const getRecordingsOfCalVideoByRoomName = async (
roomName: string
): Promise<GetRecordingsResponseSchema | undefined> => {
let dailyAppKeys: Awaited<ReturnType<typeof getDailyAppKeys>>;
try {
dailyAppKeys = await getDailyAppKeys();
} catch {
console.error("Error: Cal video provider is not installed.");
return;
}
const [videoAdapter] = await getVideoAdapters([
{
id: 0,
appId: "daily-video",
type: "daily_video",
userId: null,
user: { email: "" },
teamId: null,
key: dailyAppKeys,
invalid: false,
delegationCredentialId: null,
},
]);
return videoAdapter?.getRecordings?.(roomName);
};
const getDownloadLinkOfCalVideoByRecordingId = async (
recordingId: string
): Promise<GetAccessLinkResponseSchema | undefined> => {
let dailyAppKeys: Awaited<ReturnType<typeof getDailyAppKeys>>;
try {
dailyAppKeys = await getDailyAppKeys();
} catch {
console.error("Error: Cal video provider is not installed.");
return;
}
const [videoAdapter] = await getVideoAdapters([
{
id: 0,
appId: "daily-video",
type: "daily_video",
userId: null,
user: { email: "" },
teamId: null,
key: dailyAppKeys,
invalid: false,
delegationCredentialId: null,
},
]);
return videoAdapter?.getRecordingDownloadLink?.(recordingId);
};
const getAllTranscriptsAccessLinkFromRoomName = async (roomName: string) => {
let dailyAppKeys: Awaited<ReturnType<typeof getDailyAppKeys>>;
try {
dailyAppKeys = await getDailyAppKeys();
} catch {
console.error("Error: Cal video provider is not installed.");
return;
}
const [videoAdapter] = await getVideoAdapters([
{
id: 0,
appId: "daily-video",
type: "daily_video",
userId: null,
user: { email: "" },
teamId: null,
key: dailyAppKeys,
invalid: false,
delegationCredentialId: null,
},
]);
return videoAdapter?.getAllTranscriptsAccessLinkFromRoomName?.(roomName);
};
const getAllTranscriptsAccessLinkFromMeetingId = async (meetingId: string) => {
let dailyAppKeys: Awaited<ReturnType<typeof getDailyAppKeys>>;
try {
dailyAppKeys = await getDailyAppKeys();
} catch {
console.error("Error: Cal video provider is not installed.");
return;
}
const [videoAdapter] = await getVideoAdapters([
{
id: 0,
appId: "daily-video",
type: "daily_video",
userId: null,
user: { email: "" },
teamId: null,
key: dailyAppKeys,
invalid: false,
delegationCredentialId: null,
},
]);
return videoAdapter?.getAllTranscriptsAccessLinkFromMeetingId?.(meetingId);
};
const submitBatchProcessorTranscriptionJob = async (recordingId: string) => {
let dailyAppKeys: Awaited<ReturnType<typeof getDailyAppKeys>>;
try {
dailyAppKeys = await getDailyAppKeys();
} catch {
console.error("Error: Cal video provider is not installed.");
return;
}
const [videoAdapter] = await getVideoAdapters([
{
id: 0,
appId: "daily-video",
type: "daily_video",
userId: null,
user: { email: "" },
teamId: null,
key: dailyAppKeys,
invalid: false,
delegationCredentialId: null,
},
]);
return videoAdapter?.submitBatchProcessorJob?.({
preset: "transcript",
inParams: {
sourceType: "recordingId",
recordingId: recordingId,
},
outParams: {
s3Config: {
s3KeyTemplate: "transcript",
},
},
});
};
const getTranscriptsAccessLinkFromRecordingId = async (recordingId: string) => {
let dailyAppKeys: Awaited<ReturnType<typeof getDailyAppKeys>>;
try {
dailyAppKeys = await getDailyAppKeys();
} catch {
console.error("Error: Cal video provider is not installed.");
return;
}
const [videoAdapter] = await getVideoAdapters([
{
id: 0,
appId: "daily-video",
type: "daily_video",
userId: null,
user: { email: "" },
teamId: null,
key: dailyAppKeys,
invalid: false,
delegationCredentialId: null,
},
]);
return videoAdapter?.getTranscriptsAccessLinkFromRecordingId?.(recordingId);
};
const checkIfRoomNameMatchesInRecording = async (roomName: string, recordingId: string) => {
let dailyAppKeys: Awaited<ReturnType<typeof getDailyAppKeys>>;
try {
dailyAppKeys = await getDailyAppKeys();
} catch {
console.error("Error: Cal video provider is not installed.");
return;
}
const [videoAdapter] = await getVideoAdapters([
{
id: 0,
appId: "daily-video",
type: "daily_video",
userId: null,
user: { email: "" },
teamId: null,
key: dailyAppKeys,
invalid: false,
delegationCredentialId: null,
},
]);
return videoAdapter?.checkIfRoomNameMatchesInRecording?.(roomName, recordingId);
};
export {
getBusyVideoTimes,
createMeeting,
updateMeeting,
deleteMeeting,
getRecordingsOfCalVideoByRoomName,
getDownloadLinkOfCalVideoByRecordingId,
getAllTranscriptsAccessLinkFromRoomName,
getAllTranscriptsAccessLinkFromMeetingId,
submitBatchProcessorTranscriptionJob,
getTranscriptsAccessLinkFromRecordingId,
checkIfRoomNameMatchesInRecording,
};