feat: usage base billed - license key check - move to private api (#15222)

Co-authored-by: Omar López <zomars@me.com>
This commit is contained in:
sean-brydon
2024-07-25 13:20:24 -07:00
committed by GitHub
co-authored by Omar López
parent 53c37a9ddf
commit e16662e655
16 changed files with 586 additions and 59 deletions
+3 -3
View File
@@ -15,10 +15,10 @@
# - You can not repackage or sell the codebase
# - Acquire a commercial license to remove these terms by visiting: cal.com/sales
#
# To enable enterprise-only features, as an admin, go to /auth/setup to select your license and follow
# instructions. This environment variable is deprecated although still supported for backward compatibility.
# @see https://console.cal.com
# To enable enterprise-only features please add your envrionment variable to the .env file then make your way to /auth/setup to select your license and follow instructions.
CALCOM_LICENSE_KEY=
CALCOM_PRIVATE_API_ROUTE="https://goblin.cal.com"
# ***********************************************************************************************************
# - DATABASE ************************************************************************************************
+6 -3
View File
@@ -1,7 +1,7 @@
import type { NextMiddleware } from "next-api-middleware";
import LicenseKeyService from "@calcom/ee/common/server/LicenseKeyService";
import { hashAPIKey } from "@calcom/features/ee/api-keys/lib/apiKeys";
import checkLicense from "@calcom/features/ee/common/server/checkLicense";
import { IS_PRODUCTION } from "@calcom/lib/constants";
import prisma from "@calcom/prisma";
@@ -18,9 +18,12 @@ export const dateNotInPast = function (date: Date) {
// This verifies the apiKey and sets the user if it is valid.
export const verifyApiKey: NextMiddleware = async (req, res, next) => {
const hasValidLicense = await checkLicense(prisma);
if (!hasValidLicense && IS_PRODUCTION)
const licenseKeyService = await LicenseKeyService.create();
const hasValidLicense = await licenseKeyService.checkLicense();
if (!hasValidLicense && IS_PRODUCTION) {
return res.status(401).json({ error: "Invalid or missing CALCOM_LICENSE_KEY environment variable" });
}
// Check if the apiKey query param is provided.
if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" });
// remove the prefix from the user provided api_key. If no env set default to "cal_"
@@ -3,9 +3,9 @@ import prismaMock from "../../../../../../tests/libs/__mocks__/prismaMock";
import type { Request, Response } from "express";
import type { NextApiRequest, NextApiResponse } from "next";
import { createMocks } from "node-mocks-http";
import { describe, vi, it, expect, afterEach } from "vitest";
import { describe, vi, it, expect, afterEach, beforeEach } from "vitest";
import checkLicense from "@calcom/features/ee/common/server/checkLicense";
import LicenseKeyService from "@calcom/ee/common/server/LicenseKeyService";
import prisma from "@calcom/prisma";
import { isAdminGuard } from "~/lib/utils/isAdmin";
@@ -20,20 +20,21 @@ afterEach(() => {
vi.resetAllMocks();
});
vi.mock("@calcom/features/ee/common/server/checkLicense", () => {
return {
default: vi.fn(),
};
});
vi.mock("~/lib/utils/isAdmin", () => {
return {
isAdminGuard: vi.fn(),
};
});
vi.mock("@calcom/prisma");
vi.mock("~/lib/utils/isAdmin", () => ({
isAdminGuard: vi.fn(),
}));
describe("Verify API key", () => {
it("It should throw an error if the api key is not valid", async () => {
let service: LicenseKeyService;
beforeEach(async () => {
service = await LicenseKeyService.create();
vi.spyOn(service, "checkLicense");
});
it("should throw an error if the api key is not valid", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {},
@@ -43,7 +44,7 @@ describe("Verify API key", () => {
fn: verifyApiKey,
};
vi.mocked(checkLicense).mockResolvedValue(false);
vi.mocked(service.checkLicense).mockResolvedValue(false);
vi.mocked(isAdminGuard).mockResolvedValue({ isAdmin: false, scope: null });
const serverNext = vi.fn((next: void) => Promise.resolve(next));
@@ -53,9 +54,11 @@ describe("Verify API key", () => {
await middleware.fn(req, res, serverNext);
expect(middlewareSpy).toBeCalled();
expect(res.statusCode).toBe(401);
});
it("It should throw an error if no api key is provided", async () => {
it("should throw an error if no api key is provided", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {},
@@ -65,7 +68,7 @@ describe("Verify API key", () => {
fn: verifyApiKey,
};
vi.mocked(checkLicense).mockResolvedValue(true);
vi.mocked(service.checkLicense).mockResolvedValue(true);
vi.mocked(isAdminGuard).mockResolvedValue({ isAdmin: false, scope: null });
const serverNext = vi.fn((next: void) => Promise.resolve(next));
@@ -75,10 +78,11 @@ describe("Verify API key", () => {
await middleware.fn(req, res, serverNext);
expect(middlewareSpy).toBeCalled();
expect(res.statusCode).toBe(401);
});
it("It should set correct permissions for system-wide admin", async () => {
it("should set correct permissions for system-wide admin", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {},
@@ -97,7 +101,7 @@ describe("Verify API key", () => {
fn: verifyApiKey,
};
vi.mocked(checkLicense).mockResolvedValue(true);
vi.mocked(service.checkLicense).mockResolvedValue(true);
vi.mocked(isAdminGuard).mockResolvedValue({ isAdmin: true, scope: ScopeOfAdmin.SystemWide });
const serverNext = vi.fn((next: void) => Promise.resolve(next));
@@ -107,11 +111,12 @@ describe("Verify API key", () => {
await middleware.fn(req, res, serverNext);
expect(middlewareSpy).toBeCalled();
expect(req.isSystemWideAdmin).toBe(true);
expect(req.isOrganizationOwnerOrAdmin).toBe(false);
});
it("It should set correct permissions for org-level admin", async () => {
it("should set correct permissions for org-level admin", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {},
@@ -130,7 +135,7 @@ describe("Verify API key", () => {
fn: verifyApiKey,
};
vi.mocked(checkLicense).mockResolvedValue(true);
vi.mocked(service.checkLicense).mockResolvedValue(true);
vi.mocked(isAdminGuard).mockResolvedValue({ isAdmin: true, scope: ScopeOfAdmin.OrgOwnerOrAdmin });
const serverNext = vi.fn((next: void) => Promise.resolve(next));
@@ -140,6 +145,7 @@ describe("Verify API key", () => {
await middleware.fn(req, res, serverNext);
expect(middlewareSpy).toBeCalled();
expect(req.isSystemWideAdmin).toBe(false);
expect(req.isOrganizationOwnerOrAdmin).toBe(true);
});
@@ -1,6 +1,5 @@
"use client";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import { CreateANewLicenseKeyForm } from "@calcom/features/ee/deployment/licensekey/CreateLicenseKeyForm";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { WizardLayout, Meta } from "@calcom/ui";
@@ -12,10 +11,10 @@ import PageWrapper from "@components/PageWrapper";
const CreateNewLicenseKeyPage = () => {
const { t } = useLocale();
return (
<LicenseRequired>
<>
<Meta title={t("set_up_your_organization")} description={t("organizations_description")} />
<CreateANewLicenseKeyForm />
</LicenseRequired>
</>
);
};
const LayoutWrapper = (page: React.ReactElement) => {
@@ -30,4 +29,5 @@ CreateNewLicenseKeyPage.getLayout = LayoutWrapper;
CreateNewLicenseKeyPage.PageWrapper = PageWrapper;
export default CreateNewLicenseKeyPage;
export { getServerSideProps };
@@ -3,7 +3,7 @@ import type { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from
import type { AuthOptions, Session } from "next-auth";
import { getToken } from "next-auth/jwt";
import checkLicense from "@calcom/features/ee/common/server/checkLicense";
import LicenseKeyService from "@calcom/ee/common/server/LicenseKeyService";
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
@@ -65,7 +65,8 @@ export async function getServerSession(options: {
return null;
}
const hasValidLicense = await checkLicense(prisma);
const licenseKeyService = await LicenseKeyService.create();
const hasValidLicense = await licenseKeyService.checkLicense();
let upId = token.upId;
@@ -7,7 +7,7 @@ import CredentialsProvider from "next-auth/providers/credentials";
import EmailProvider from "next-auth/providers/email";
import GoogleProvider from "next-auth/providers/google";
import checkLicense from "@calcom/features/ee/common/server/checkLicense";
import LicenseKeyService from "@calcom/ee/common/server/LicenseKeyService";
import createUsersAndConnectToOrg from "@calcom/features/ee/dsync/lib/users/createUsersAndConnectToOrg";
import ImpersonationProvider from "@calcom/features/ee/impersonation/lib/ImpersonationProvider";
import { getOrgFullOrigin, subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains";
@@ -619,7 +619,9 @@ export const AUTH_OPTIONS: AuthOptions = {
},
async session({ session, token, user }) {
log.debug("callbacks:session - Session callback called", safeStringify({ session, token, user }));
const hasValidLicense = await checkLicense(prisma);
const licenseKeyService = await LicenseKeyService.create();
const hasValidLicense = await licenseKeyService.checkLicense();
const profileId = token.profileId;
const calendsoSession: Session = {
...session,
@@ -0,0 +1,190 @@
import "../../../../../tests/libs/__mocks__/prisma";
import * as cache from "memory-cache";
import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
import { getDeploymentKey } from "../../deployment/lib/getDeploymentKey";
import { createSignature, generateNonce } from "./private-api-utils";
const baseUrl = "http://test-api.com";
const licenseKey = "test-license-key";
/** So we can override in specific test below */
process.env.NEXT_PUBLIC_IS_E2E = "0";
/** All fetch call in LicenseKeyService will fail without this */
process.env.CAL_SIGNATURE_TOKEN = "dummy";
/**
* This is needed to ensure the constants and env are fresh.
* If not, we would need to override constants on each scenario with differing constants/env vars.
*/
async function getLicenseKeyService() {
return (await import("./LicenseKeyService")).default;
}
async function stubEnvAndReload(key: string, value: string) {
// We set env variable
vi.stubEnv(key, value);
// We refresh constants and prevent cached modules.
// @see https://github.com/vitest-dev/vitest/issues/4232#issuecomment-1745452522
vi.resetModules();
await import("@calcom/lib/constants");
}
// Mock dependencies
vi.mock("memory-cache", () => ({
get: vi.fn(),
put: vi.fn(),
}));
vi.mock("../../deployment/lib/getDeploymentKey", () => ({
getDeploymentKey: vi.fn(),
}));
vi.mock("./private-api-utils", () => ({
generateNonce: vi.fn(),
createSignature: vi.fn(),
}));
const BASE_HEADERS = {
"Content-Type": "application/json",
};
describe("LicenseKeyService", () => {
beforeEach(async () => {
vi.mocked(getDeploymentKey).mockResolvedValue(licenseKey);
});
afterEach(() => {
vi.resetAllMocks();
vi.unstubAllEnvs();
});
describe("create", () => {
it("should create an instance of LicenseKeyService", async () => {
const LicenseKeyService = await getLicenseKeyService();
const service = await LicenseKeyService.create();
expect(service).toBeInstanceOf(LicenseKeyService);
});
});
describe("incrementUsage", () => {
it("should call the incrementUsage API and return the response", async () => {
const mockResponse = { success: true };
const fetchSpy = vi.spyOn(global, "fetch").mockResolvedValue({
json: vi.fn().mockResolvedValue(mockResponse),
} as any);
vi.mocked(generateNonce).mockReturnValue("mocked-nonce");
vi.mocked(createSignature).mockReturnValue("mocked-signature");
stubEnvAndReload("CALCOM_PRIVATE_API_ROUTE", baseUrl);
const LicenseKeyService = await getLicenseKeyService();
const service = await LicenseKeyService.create();
const response = await service.incrementUsage();
expect(response).toEqual(mockResponse);
expect(fetchSpy).toHaveBeenCalledWith(`${baseUrl}/v1/license/usage/increment?event=booking`, {
body: undefined,
headers: {
...BASE_HEADERS,
nonce: "mocked-nonce",
signature: "mocked-signature",
"x-cal-license-key": "test-license-key",
},
method: "POST",
mode: "cors",
});
});
it("should throw an error if the API call fails", async () => {
const fetchSpy = vi.spyOn(global, "fetch").mockRejectedValue(new Error("API Failure"));
vi.mocked(generateNonce).mockReturnValue("mocked-nonce");
vi.mocked(createSignature).mockReturnValue("mocked-signature");
stubEnvAndReload("CALCOM_PRIVATE_API_ROUTE", baseUrl);
const LicenseKeyService = await getLicenseKeyService();
const service = await LicenseKeyService.create();
await expect(service.incrementUsage()).rejects.toThrow("API Failure");
expect(fetchSpy).toHaveBeenCalledWith(`${baseUrl}/v1/license/usage/increment?event=booking`, {
body: undefined,
headers: {
...BASE_HEADERS,
nonce: "mocked-nonce",
signature: "mocked-signature",
"x-cal-license-key": "test-license-key",
},
method: "POST",
mode: "cors",
});
});
});
describe("checkLicense", () => {
it("should return true if NEXT_PUBLIC_IS_E2E is set", async () => {
stubEnvAndReload("NEXT_PUBLIC_IS_E2E", "1");
const LicenseKeyService = await getLicenseKeyService();
const service = await LicenseKeyService.create();
const result = await service.checkLicense();
expect(result).toBe(true);
});
it("should return cached response if available", async () => {
const url = `${baseUrl}/v1/license/${licenseKey}`;
vi.mocked(cache.get).mockReturnValue(true);
stubEnvAndReload("CALCOM_PRIVATE_API_ROUTE", baseUrl);
const LicenseKeyService = await getLicenseKeyService();
const service = await LicenseKeyService.create();
const result = await service.checkLicense();
expect(result).toBe(true);
expect(cache.get).toHaveBeenCalledWith(url);
});
it("should fetch license validity from API if not cached", async () => {
const url = `${baseUrl}/v1/license/${licenseKey}`;
vi.mocked(cache.get).mockReturnValue(null);
const mockResponse = { status: true };
const fetchSpy = vi.spyOn(global, "fetch").mockResolvedValue({
json: vi.fn().mockResolvedValue(mockResponse),
} as any);
vi.mocked(generateNonce).mockReturnValue("mocked-nonce");
vi.mocked(createSignature).mockReturnValue("mocked-signature");
stubEnvAndReload("CALCOM_PRIVATE_API_ROUTE", baseUrl);
const LicenseKeyService = await getLicenseKeyService();
const service = await LicenseKeyService.create();
const result = await service.checkLicense();
expect(result).toBe(true);
expect(cache.get).toHaveBeenCalledWith(url);
expect(fetchSpy).toHaveBeenCalledWith(url, {
body: undefined,
headers: {
...BASE_HEADERS,
nonce: "mocked-nonce",
signature: "mocked-signature",
"x-cal-license-key": "test-license-key",
},
mode: "cors",
});
});
it("should return false if API call fails", async () => {
const url = `${baseUrl}/v1/license/${licenseKey}`;
stubEnvAndReload("CALCOM_PRIVATE_API_ROUTE", baseUrl);
vi.mocked(cache.get).mockReturnValue(null);
vi.mocked(generateNonce).mockReturnValue("mocked-nonce");
vi.mocked(createSignature).mockReturnValue("mocked-signature");
const fetchSpy = vi.spyOn(global, "fetch").mockRejectedValue(new Error("API Failure"));
const LicenseKeyService = await getLicenseKeyService();
const service = await LicenseKeyService.create();
const result = await service.checkLicense();
expect(result).toBe(false);
expect(fetchSpy).toHaveBeenCalledWith(url, {
body: undefined,
headers: {
...BASE_HEADERS,
nonce: "mocked-nonce",
signature: "mocked-signature",
"x-cal-license-key": "test-license-key",
},
mode: "cors",
});
});
});
});
@@ -0,0 +1,97 @@
import * as cache from "memory-cache";
import { CALCOM_PRIVATE_API_ROUTE } from "@calcom/lib/constants";
import prisma from "@calcom/prisma";
import { getDeploymentKey } from "../../deployment/lib/getDeploymentKey";
import { generateNonce, createSignature } from "./private-api-utils";
export enum UsageEvent {
BOOKING = "booking",
USER = "user",
}
class LicenseKeyService {
private readonly baseUrl = CALCOM_PRIVATE_API_ROUTE;
private readonly licenseKey: string;
public readonly CACHING_TIME = 86_400_000; // 24 hours in milliseconds
// Private constructor to prevent direct instantiation
private constructor(licenseKey: string) {
this.baseUrl = CALCOM_PRIVATE_API_ROUTE;
this.licenseKey = licenseKey;
}
// Static async factory method
public static async create(): Promise<LicenseKeyService> {
const licenseKey = await getDeploymentKey(prisma);
return new LicenseKeyService(licenseKey);
}
private async fetcher({
url,
body,
options = {},
}: {
url: string;
body?: Record<string, unknown>;
options?: RequestInit;
}): Promise<Response> {
const nonce = generateNonce();
const signatureToken = process.env.CAL_SIGNATURE_TOKEN;
if (!signatureToken) {
throw new Error("CAL_SIGNATURE_TOKEN needs to be set");
}
const signature = createSignature(body || {}, nonce, signatureToken);
const headers = {
...options.headers,
"Content-Type": "application/json",
nonce: nonce,
signature: signature,
"x-cal-license-key": this.licenseKey,
};
return await fetch(url, {
...options,
headers: headers,
body: JSON.stringify(body),
});
}
async incrementUsage(usageEvent?: UsageEvent) {
try {
const response = await this.fetcher({
url: `${this.baseUrl}/v1/license/usage/increment?event=${usageEvent ?? UsageEvent.BOOKING}`,
options: {
method: "POST",
mode: "cors",
},
});
return await response.json();
} catch (error) {
console.error("Incrementing usage failed:", error);
throw error;
}
}
async checkLicense(): Promise<boolean> {
/** We skip for E2E testing */
if (process.env.NEXT_PUBLIC_IS_E2E === "1") return true;
/** We check first on env */
const url = `${this.baseUrl}/v1/license/${this.licenseKey}`;
const cachedResponse = cache.get(url);
if (cachedResponse) return cachedResponse;
try {
const response = await this.fetcher({ url, options: { mode: "cors" } });
const data = await response.json();
cache.put(url, data.stauts, this.CACHING_TIME);
return data.status;
} catch (error) {
console.error("Check license failed:", error);
return false;
}
}
}
export default LicenseKeyService;
@@ -0,0 +1,13 @@
import crypto from "crypto";
export const generateNonce = (): string => {
return crypto.randomBytes(16).toString("hex");
};
// Utility function to create a signature
export const createSignature = (body: Record<string, unknown>, nonce: string, secretKey: string): string => {
return crypto
.createHmac("sha256", secretKey)
.update(JSON.stringify(body) + nonce)
.digest("hex");
};
@@ -1,9 +1,13 @@
import type { PrismaClient } from "@calcom/prisma";
export async function getDeploymentKey(prisma: PrismaClient) {
if (process.env.CALCOM_LICENSE_KEY) {
return process.env.CALCOM_LICENSE_KEY;
}
const deployment = await prisma.deployment.findUnique({
where: { id: 1 },
select: { licenseKey: true },
});
return deployment?.licenseKey || process.env.CALCOM_LICENSE_KEY || "";
return deployment?.licenseKey || "";
}
@@ -72,7 +72,7 @@ const CreateANewLicenseKeyFormChild = ({ session }: { session: Ensure<SessionCon
function calculateMonthlyPrice() {
const occurrence = watchedBillingPeriod === "MONTHLY" ? 1 : 12;
const sum = watchedEntityCount * watchedEntityPrice * occurrence;
const sum = watchedEntityCount * watchedEntityPrice;
return `$ ${sum / 100} / ${occurrence} months`;
}
+2
View File
@@ -153,6 +153,8 @@ export const CLOUDFLARE_SITE_ID = process.env.NEXT_PUBLIC_CLOUDFLARE_SITEKEY;
export const MINIMUM_NUMBER_OF_ORG_SEATS = 30;
export const ORG_SELF_SERVE_ENABLED = process.env.NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED === "1";
export const ORG_MINIMUM_PUBLISHED_TEAMS_SELF_SERVE = 0;
export const CALCOM_PRIVATE_API_ROUTE = process.env.CALCOM_PRIVATE_API_ROUTE || "https://goblin.cal.com";
/**
* The maximum number of days we should check for if we don't find all required bookable days
* Counter start from current day and we would like to not go beyond 2 months(max days possible) from current day.
@@ -0,0 +1,32 @@
import { Prisma } from "@prisma/client";
import LicenseKeyService, { UsageEvent } from "@calcom/ee/common/server/LicenseKeyService";
export function usageTrackingExtention() {
return Prisma.defineExtension({
query: {
booking: {
async create({ args, query }) {
try {
const licenseKeyService = await LicenseKeyService.create();
await licenseKeyService.incrementUsage();
} catch (e) {
console.log(e);
}
return query(args);
},
},
user: {
async create({ args, query }) {
try {
const licenseKeyService = await LicenseKeyService.create();
await licenseKeyService.incrementUsage(UsageEvent.USER);
} catch (e) {
console.log(e);
}
return query(args);
},
},
},
});
}
+3
View File
@@ -4,6 +4,7 @@ import { withAccelerate } from "@prisma/extension-accelerate";
import { bookingIdempotencyKeyExtension } from "./extensions/booking-idempotency-key";
import { excludePendingPaymentsExtension } from "./extensions/exclude-pending-payment-teams";
import { usageTrackingExtention } from "./extensions/usage-tracking";
import { bookingReferenceMiddleware } from "./middleware";
const prismaOptions: Prisma.PrismaClientOptions = {};
@@ -21,6 +22,7 @@ const prismaWithoutClientExtensions =
export const customPrisma = (options?: Prisma.PrismaClientOptions) =>
new PrismaClientWithoutExtension({ ...prismaOptions, ...options })
.$extends(usageTrackingExtention())
.$extends(excludePendingPaymentsExtension())
.$extends(bookingIdempotencyKeyExtension())
.$extends(withAccelerate());
@@ -32,6 +34,7 @@ bookingReferenceMiddleware(prismaWithoutClientExtensions);
// FIXME: Due to some reason, there are types failing in certain places due to the $extends. Fix it and then enable it
// Specifically we get errors like `Type 'string | Date | null | undefined' is not assignable to type 'Exact<string | Date | null | undefined, string | Date | null | undefined>'`
const prismaWithClientExtensions = prismaWithoutClientExtensions
.$extends(usageTrackingExtention())
.$extends(excludePendingPaymentsExtension())
.$extends(bookingIdempotencyKeyExtension())
.$extends(withAccelerate());
@@ -1,6 +1,8 @@
import * as crypto from "crypto";
import { z } from "zod";
import { CALCOM_PRIVATE_API_ROUTE } from "@calcom/lib/constants";
import type { TrpcSessionUser } from "../../../trpc";
import type { TCreateSelfHostedLicenseSchema } from "./createSelfHostedLicenseKey.schema";
@@ -49,7 +51,7 @@ const fetchWithSignature = async (
};
const createSelfHostedInstance = async ({ input, ctx }: GetOptions) => {
const privateApiUrl = process.env.CALCOM_PRIVATE_API_ROUTE;
const privateApiUrl = CALCOM_PRIVATE_API_ROUTE;
const signatureToken = process.env.CAL_SIGNATURE_TOKEN;
if (!privateApiUrl || !signatureToken) {
+194 -22
View File
@@ -4216,7 +4216,7 @@ __metadata:
languageName: unknown
linkType: soft
"@calcom/atoms@*, @calcom/atoms@workspace:packages/platform/atoms":
"@calcom/atoms@*, @calcom/atoms@workspace:^, @calcom/atoms@workspace:packages/platform/atoms":
version: 0.0.0-use.local
resolution: "@calcom/atoms@workspace:packages/platform/atoms"
dependencies:
@@ -5249,6 +5249,15 @@ __metadata:
languageName: unknown
linkType: soft
"@calcom/skype@workspace:packages/app-store/skype":
version: 0.0.0-use.local
resolution: "@calcom/skype@workspace:packages/app-store/skype"
dependencies:
"@calcom/lib": "*"
"@calcom/types": "*"
languageName: unknown
linkType: soft
"@calcom/storybook@workspace:apps/storybook":
version: 0.0.0-use.local
resolution: "@calcom/storybook@workspace:apps/storybook"
@@ -5664,6 +5673,7 @@ __metadata:
dependencies:
"@algora/sdk": ^0.1.2
"@calcom/app-store": "*"
"@calcom/atoms": "workspace:^"
"@calcom/config": "*"
"@calcom/dayjs": "*"
"@calcom/embed-react": "workspace:^"
@@ -5709,6 +5719,7 @@ __metadata:
"@vercel/og": ^0.5.0
autoprefixer: ^10.4.12
bcryptjs: ^2.4.3
class-variance-authority: ^0.7.0
clsx: ^1.2.1
cobe: ^0.4.1
concurrently: ^7.6.0
@@ -5721,6 +5732,7 @@ __metadata:
env-cmd: ^10.1.0
eslint: ^8.34.0
fathom-client: ^3.5.0
framer-motion: ^11.0.25
globby: ^13.1.3
graphql: ^16.8.0
graphql-codegen: ^0.4.0
@@ -5730,7 +5742,7 @@ __metadata:
i18n-unused: ^0.13.0
iframe-resizer-react: ^1.1.0
keen-slider: ^6.8.0
lucide-react: ^0.171.0
lucide-react: ^0.364.0
micro: ^10.0.1
next: ^14.1.3
next-auth: ^4.22.1
@@ -5742,7 +5754,7 @@ __metadata:
prism-react-renderer: ^1.3.5
react: ^18.2.0
react-confetti: ^6.0.1
react-datocms: ^3.1.0
react-datocms: ^5.0.3
react-device-detect: ^2.2.2
react-dom: ^18.2.0
react-fast-marquee: ^1.6.4
@@ -5752,8 +5764,10 @@ __metadata:
react-live-chat-loader: ^2.8.1
react-markdown: ^9.0.1
react-merge-refs: 1.1.0
react-parallax-tilt: ^1.7.226
react-resize-detector: ^9.1.0
react-twemoji: ^0.3.0
react-twitter-embed: ^4.0.4
react-use-measure: ^2.1.1
react-wrap-balancer: ^1.0.0
remark: ^14.0.2
@@ -9507,6 +9521,59 @@ __metadata:
languageName: node
linkType: hard
"@mux/mux-player-react@npm:*":
version: 2.8.0
resolution: "@mux/mux-player-react@npm:2.8.0"
dependencies:
"@mux/mux-player": 2.8.0
"@mux/playback-core": 0.25.0
prop-types: ^15.7.2
peerDependencies:
"@types/react": ^17.0.0 || ^18
react: ^17.0.2 || ^18
react-dom: ^17.0.2 || ^18
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
checksum: b4891a151ff92bbee3b9d2e0411c21be07b976c30f6e13c30b30cda97eeb5d4a88bb3d78fd58dac447b05bb12563234a9a5fbba3bdf79e0c274575cf9bb020cd
languageName: node
linkType: hard
"@mux/mux-player@npm:2.8.0":
version: 2.8.0
resolution: "@mux/mux-player@npm:2.8.0"
dependencies:
"@mux/mux-video": 0.20.0
"@mux/playback-core": 0.25.0
media-chrome: ~3.2.3
checksum: 6a8e410456a325c2e59ef3ebb4f3efba69596a4df3e38e9ccb332af23fe16c571a8271c51e97ea5f003352f09f63fdb311ff548c0b2f613b7ca4c47fa68fcade
languageName: node
linkType: hard
"@mux/mux-video@npm:0.20.0":
version: 0.20.0
resolution: "@mux/mux-video@npm:0.20.0"
dependencies:
"@mux/playback-core": 0.25.0
castable-video: ~1.0.9
custom-media-element: ~1.3.1
media-tracks: ~0.3.2
checksum: 2f316a6697ff63dbcc5428216600ced1db14e22c195567c69e826b29b2752b9aeee785f073bc2eef71d7422b696370608d9d081b8e24b5332dfeb57bf0839791
languageName: node
linkType: hard
"@mux/playback-core@npm:0.25.0":
version: 0.25.0
resolution: "@mux/playback-core@npm:0.25.0"
dependencies:
hls.js: ~1.5.11
mux-embed: ~5.2.0
checksum: 37175985f7ec7df0b2f24e9b6e3df9c39f94da81b0f1e59b989dd9d04d9451dae2ea6e06d4208a673e532ea2351c39c2d2ec0e149cde60a9a09b9dbd00ea2866
languageName: node
linkType: hard
"@ndelangen/get-tarball@npm:^3.0.7":
version: 3.0.9
resolution: "@ndelangen/get-tarball@npm:3.0.9"
@@ -21502,6 +21569,15 @@ __metadata:
languageName: node
linkType: hard
"castable-video@npm:~1.0.9":
version: 1.0.10
resolution: "castable-video@npm:1.0.10"
dependencies:
custom-media-element: ~1.3.2
checksum: 5b7a27aacf305f40c6867e96e773f4290260f6eb8b01fe1f209506f8f6dec4cd099f3e35ee57de098829e4bcc688ccdc2cbb2fc86c509712dd573eadf66d42cc
languageName: node
linkType: hard
"ccount@npm:^2.0.0":
version: 2.0.1
resolution: "ccount@npm:2.0.1"
@@ -21966,6 +22042,15 @@ __metadata:
languageName: node
linkType: hard
"class-variance-authority@npm:^0.7.0":
version: 0.7.0
resolution: "class-variance-authority@npm:0.7.0"
dependencies:
clsx: 2.0.0
checksum: e7fd1fab433ef06f52a1b7b241b70b4a185864deef199d3b0a2c3412f1cc179517288264c383f3b971a00d76811625fc8f7ffe709e6170219e88cd7368f08a20
languageName: node
linkType: hard
"classnames@npm:^2.2.5, classnames@npm:^2.2.6":
version: 2.3.2
resolution: "classnames@npm:2.3.2"
@@ -22238,6 +22323,13 @@ __metadata:
languageName: node
linkType: hard
"clsx@npm:2.0.0":
version: 2.0.0
resolution: "clsx@npm:2.0.0"
checksum: a2cfb2351b254611acf92faa0daf15220f4cd648bdf96ce369d729813b85336993871a4bf6978ddea2b81b5a130478339c20d9d0b5c6fc287e5147f0c059276e
languageName: node
linkType: hard
"clsx@npm:^1.1.1":
version: 1.1.1
resolution: "clsx@npm:1.1.1"
@@ -23543,6 +23635,13 @@ __metadata:
languageName: node
linkType: hard
"custom-media-element@npm:~1.3.1, custom-media-element@npm:~1.3.2":
version: 1.3.2
resolution: "custom-media-element@npm:1.3.2"
checksum: 9ec2ff88c3c5ba7d20b9484c0359b4cfe08ad6804f3116934ffbae2f2e0e04d2a832beb6f0bec56027a969ab366727d217406f750890613c988de6de5330cb4f
languageName: node
linkType: hard
"d3-array@npm:2 - 3, d3-array@npm:2.10.0 - 3, d3-array@npm:^3.1.6":
version: 3.2.3
resolution: "d3-array@npm:3.2.3"
@@ -27590,6 +27689,26 @@ __metadata:
languageName: node
linkType: hard
"framer-motion@npm:^11.0.25":
version: 11.3.12
resolution: "framer-motion@npm:11.3.12"
dependencies:
tslib: ^2.4.0
peerDependencies:
"@emotion/is-prop-valid": "*"
react: ^18.0.0
react-dom: ^18.0.0
peerDependenciesMeta:
"@emotion/is-prop-valid":
optional: true
react:
optional: true
react-dom:
optional: true
checksum: d13fa5fd2a21e73846a1171b8e6cad0be214b48f0c9b91070397975977758a98c66ea55d3e8dc027d959831027d76d0d8044cf41c49942cc3362a7b0cce7d4a0
languageName: node
linkType: hard
"fresh@npm:0.5.2, fresh@npm:^0.5.2":
version: 0.5.2
resolution: "fresh@npm:0.5.2"
@@ -29262,6 +29381,13 @@ __metadata:
languageName: node
linkType: hard
"hls.js@npm:~1.5.11":
version: 1.5.13
resolution: "hls.js@npm:1.5.13"
checksum: 534b48b18638fefe7788fd31d92b0b4e3820271b470fbd18c29cd46d51e6381b6f011d732f6196d4cda3148c81d8cb7740e1d1d0f2da0031e808b468198142f3
languageName: node
linkType: hard
"hmac-drbg@npm:^1.0.1":
version: 1.0.1
resolution: "hmac-drbg@npm:1.0.1"
@@ -34082,15 +34208,6 @@ __metadata:
languageName: node
linkType: hard
"lucide-react@npm:^0.171.0":
version: 0.171.0
resolution: "lucide-react@npm:0.171.0"
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0
checksum: 768ffe368c52a518ee339203d86ff4479989ab4d79c0716f721900c4bb7392ef6ff7a14807f6a685abd74d27f4c1778170bff77a0ab4c3e06c17944b557d8300
languageName: node
linkType: hard
"lucide-react@npm:^0.364.0":
version: 0.364.0
resolution: "lucide-react@npm:0.364.0"
@@ -34660,6 +34777,20 @@ __metadata:
languageName: node
linkType: hard
"media-chrome@npm:~3.2.3":
version: 3.2.4
resolution: "media-chrome@npm:3.2.4"
checksum: 6401f509f4075cd7d02f8a479e81bcb084cd06104aa17fd7ebe6623bd171eaa52e1e1333c1895ad84c1f794ea3889cca13db52dd49b64129d67dd8be558d6eb2
languageName: node
linkType: hard
"media-tracks@npm:~0.3.2":
version: 0.3.3
resolution: "media-tracks@npm:0.3.3"
checksum: 4795af3f171d7ad3a68d1ac1c1e8166a735244fe57d3fc0ec53b1c7410799e524756fc0bfb389632aebb148436b03baade36ca34a3f5377776c3968f6d2cc580
languageName: node
linkType: hard
"media-typer@npm:0.3.0":
version: 0.3.0
resolution: "media-typer@npm:0.3.0"
@@ -36037,6 +36168,13 @@ __metadata:
languageName: node
linkType: hard
"mux-embed@npm:~5.2.0":
version: 5.2.1
resolution: "mux-embed@npm:5.2.1"
checksum: ec34d3e003e2520be6d1386ca2383daa198a1fa3a469ca3f06c11a742d6ab136eef4944c37e8f2dcaa0585e1b0a6dfc119e3cf2302c7eb0e7f2ebc7c1fc95fbd
languageName: node
linkType: hard
"mysql2@npm:3.9.1":
version: 3.9.1
resolution: "mysql2@npm:3.9.1"
@@ -40345,20 +40483,21 @@ __metadata:
languageName: node
linkType: hard
"react-datocms@npm:^3.1.0":
version: 3.1.4
resolution: "react-datocms@npm:3.1.4"
"react-datocms@npm:^5.0.3":
version: 5.0.3
resolution: "react-datocms@npm:5.0.3"
dependencies:
"@mux/mux-player-react": "*"
datocms-listen: ^0.1.9
datocms-structured-text-generic-html-renderer: ^2.0.1
datocms-structured-text-utils: ^2.0.1
react-intersection-observer: ^8.33.1
react-intersection-observer: ^9.4.3
react-string-replace: ^1.1.0
universal-base64: ^2.1.0
use-deep-compare-effect: ^1.6.1
peerDependencies:
react: ">= 16.12.0"
checksum: 54aba12aef4937175c2011548a8a576c96c8d8a596e84d191826910624c1d596e76a49782689dc236388a10803b02e700ac820cb7500cca7fd147a81f6c544c3
checksum: 22c20152afb54424acfe967a2c8c525cd9f132a33374f2aba0231f16ea64dade389b096e2dac8de9ffded612bc32e9891d725609ee947639fe1cef907cb143f5
languageName: node
linkType: hard
@@ -40610,12 +40749,16 @@ __metadata:
languageName: node
linkType: hard
"react-intersection-observer@npm:^8.33.1":
version: 8.34.0
resolution: "react-intersection-observer@npm:8.34.0"
"react-intersection-observer@npm:^9.4.3":
version: 9.13.0
resolution: "react-intersection-observer@npm:9.13.0"
peerDependencies:
react: ^15.0.0 || ^16.0.0 || ^17.0.0|| ^18.0.0
checksum: 7713fecfd1512c7f5a60f9f0bf15403b8f8bbd4110bcafaeaea6de36a0e0eb60368c3638f99e9c97b75ad8fc787ea48c241dcb5c694f821d7f2976f709082cc5
react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
react-dom:
optional: true
checksum: ee2a163b078923c1556814834f83d7c3ea3e8a9d3ceef974351fd52afe12163518ec57cb2eb0f6544ac255ac4b64d2e7652bc5d60c255159c0f25bda57a565b6
languageName: node
linkType: hard
@@ -40725,6 +40868,16 @@ __metadata:
languageName: node
linkType: hard
"react-parallax-tilt@npm:^1.7.226":
version: 1.7.232
resolution: "react-parallax-tilt@npm:1.7.232"
peerDependencies:
react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
checksum: 8c866c9c06315aeaf1eddd4e5265db706e010d788667c337be552cbf8786e8b39f19cafc529be6c69d925e25b31705c52398e90bd89a805bf8914bbb46a8ebdb
languageName: node
linkType: hard
"react-phone-input-2@npm:^2.15.1":
version: 2.15.1
resolution: "react-phone-input-2@npm:2.15.1"
@@ -41139,6 +41292,18 @@ __metadata:
languageName: node
linkType: hard
"react-twitter-embed@npm:^4.0.4":
version: 4.0.4
resolution: "react-twitter-embed@npm:4.0.4"
dependencies:
scriptjs: ^2.5.9
peerDependencies:
react: ^16.0.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0
checksum: cdb3c5bd04c4da0efa767476be47c0a3865fb6335f2a1b9e242170167b51615c38164223278cef60c77143c4bac27ba582cbea054d0af3f138104fa5ec537c4c
languageName: node
linkType: hard
"react-universal-interface@npm:^0.6.2":
version: 0.6.2
resolution: "react-universal-interface@npm:0.6.2"
@@ -42963,6 +43128,13 @@ __metadata:
languageName: node
linkType: hard
"scriptjs@npm:^2.5.9":
version: 2.5.9
resolution: "scriptjs@npm:2.5.9"
checksum: fc84cb6b60b6fb9aa6f1b3bc59fc94b233bd5241ed3a04233579014382b5eb60640269c87d8657902acc09f9b785ee33230c218627cea00e653564bda8f5acb6
languageName: node
linkType: hard
"scuid@npm:^1.1.0":
version: 1.1.0
resolution: "scuid@npm:1.1.0"