Compare commits

...
Author SHA1 Message Date
github-actions[bot]andLauris Skraucis 979e1fd28b chore: version packages (#22143)
* chore: version packages

* Update package.json

* Update CHANGELOG.md

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com>
2025-06-30 07:51:11 +00:00
Lauris Skraucis 3437c4331d fix: changesets atoms size (#22134)
* fix: changesets atoms npm package size

* test changesets entry
2025-06-30 07:22:09 +00:00
github-actions[bot] 616d62094c chore: version packages (#22116)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-29 13:25:48 +02:00
ce7ff1c43c fix(signup): add missing key prop to feature list (#21437) (#21506)
* fix(signup): add missing key prop to feature list (#21437)

* refactor

---------

Co-authored-by: Kartik Saini <41051387+kart1ka@users.noreply.github.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2025-06-28 09:27:25 +00:00
Romit e27e41bc16 fix (#22112) 2025-06-28 14:30:54 +05:30
Hariom Balhara 93e183e6c3 fix: Zoom - More logging and fix for unexpected errors seen in logs (#22118)
* fix: Use WEBAPP_URL_FOR_OAUTH to be able to use http://localhost:3000
for local testing

* In case of JSON.parse crash that could happen if zoom reponsed with xml

* Fix cases where auto_recording and default_password_for_scheduled_meetings could be nullish
2025-06-28 08:11:25 +00:00
Lauris Skraucis 88a2bd3317 fix: changesets release memory (#22107)
* chore: increase instance size for changesets

* test release

* Update CHANGELOG.md
2025-06-28 04:37:47 +00:00
Keith Williams 45caaa55b0 chore: Use DBA team for prisma CODEOWNER (#22109) 2025-06-27 23:01:32 +00:00
14 changed files with 123 additions and 49 deletions
+1 -1
View File
@@ -24,7 +24,7 @@
/packages/lib/server/getLuckyUser.ts @calcom/Foundation /packages/lib/server/getLuckyUser.ts @calcom/Foundation
/packages/lib/slots.ts @calcom/Foundation /packages/lib/slots.ts @calcom/Foundation
/packages/platform/**/* @calcom/Platform @calcom/Foundation /packages/platform/**/* @calcom/Platform @calcom/Foundation
/packages/prisma/**/* @calcom/Foundation /packages/prisma/**/* @calcom/DBA
/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts @calcom/Foundation /packages/trpc/server/routers/viewer/bookings/confirm.handler.ts @calcom/Foundation
/packages/trpc/server/routers/viewer/bookings/get.handler.ts @calcom/Foundation /packages/trpc/server/routers/viewer/bookings/get.handler.ts @calcom/Foundation
/packages/trpc/server/routers/viewer/slots/**/* @calcom/Foundation /packages/trpc/server/routers/viewer/slots/**/* @calcom/Foundation
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
contents: write # to create release by changesets/action below contents: write # to create release by changesets/action below
pull-requests: write # to create pull request by changesets/action below pull-requests: write # to create pull request by changesets/action below
name: Release name: Release
runs-on: ubuntu-latest runs-on: buildjet-4vcpu-ubuntu-2204
steps: steps:
- name: Checkout Repo - name: Checkout Repo
uses: actions/checkout@v4 uses: actions/checkout@v4
+16 -18
View File
@@ -691,25 +691,23 @@ export default function Signup({
/> />
</div> </div>
<div className="mr-12 mt-8 hidden h-full w-full grid-cols-3 gap-4 overflow-hidden lg:grid"> <div className="mr-12 mt-8 hidden h-full w-full grid-cols-3 gap-4 overflow-hidden lg:grid">
{FEATURES.map((feature) => ( {FEATURES.map((feature, index) => (
<> <div key={index} className="max-w-52 mb-8 flex flex-col leading-none sm:mb-0">
<div className="max-w-52 mb-8 flex flex-col leading-none sm:mb-0"> <div className="text-emphasis items-center">
<div className="text-emphasis items-center"> <Icon name={feature.icon} className="mb-1 h-4 w-4" />
<Icon name={feature.icon} className="mb-1 h-4 w-4" /> <span className="text-sm font-medium">{t(feature.title)}</span>
<span className="text-sm font-medium">{t(feature.title)}</span>
</div>
<div className="text-subtle text-sm">
<p>
{t(
feature.description,
feature.i18nOptions && {
...feature.i18nOptions,
}
)}
</p>
</div>
</div> </div>
</> <div className="text-subtle text-sm">
<p>
{t(
feature.description,
feature.i18nOptions && {
...feature.i18nOptions,
}
)}
</p>
</div>
</div>
))} ))}
</div> </div>
</div> </div>
+1 -1
View File
@@ -82,7 +82,7 @@
"docker-stop-api": "docker ps --filter 'ancestor=cal-api' -q | xargs docker stop", "docker-stop-api": "docker ps --filter 'ancestor=cal-api' -q | xargs docker stop",
"changesets-add": "yarn changeset add", "changesets-add": "yarn changeset add",
"changesets-version": "yarn changeset version", "changesets-version": "yarn changeset version",
"changesets-release": "NODE_OPTIONS='--max_old_space_size=8192' turbo run build --filter=@calcom/atoms && yarn changeset publish" "changesets-release": "NODE_OPTIONS='--max_old_space_size=12288' turbo run build --filter=@calcom/atoms && yarn changeset publish"
}, },
"devDependencies": { "devDependencies": {
"@changesets/changelog-github": "^0.5.1", "@changesets/changelog-github": "^0.5.1",
@@ -1,7 +1,12 @@
/** /**
* Manages OAuth2.0 tokens for an app and resourceOwner. It automatically refreshes the token when needed. * Manages OAuth2.0 as well as JWT tokens(For JWT tokens, only Google Calendar use it at the moment) for an app and resourceOwner.
* It is aware of the credential sync endpoint and can sync the token from the third party source. * What it does
* It is unaware of Prisma and App logic. It is just a utility to manage OAuth2.0 tokens with life cycle methods * - It automatically refreshes the token if needed when making a request.
* - It is aware of the credential sync endpoint and can sync the token from the third party source.
* - It is kept unaware of Prisma and App logic. It is just a utility to manage OAuth2.0 tokens with life cycle methods
*
* What it doesn't do yet
* - It doesn't have a flow to re-send the request if the access-token had been communicated as invalid after making the request itself. It relies on the caller to make the next request in which it will actually refresh the token.
* *
* For a recommended usage example, see Zoom VideoApiAdapter.ts * For a recommended usage example, see Zoom VideoApiAdapter.ts
*/ */
@@ -89,7 +94,13 @@ export class OAuthManager {
currentTokenObject, currentTokenObject,
fetchNewTokenObject, fetchNewTokenObject,
updateTokenObject, updateTokenObject,
/**
* The fn must not crash. It is the responsibility of the caller to handle any error and appropriately decide what to return
*/
isTokenObjectUnusable, isTokenObjectUnusable,
/**
* The fn must not crash. It is the responsibility of the caller to handle any error and appropriately decide what to return
*/
isAccessTokenUnusable, isAccessTokenUnusable,
invalidateTokenObject, invalidateTokenObject,
expireAccessToken, expireAccessToken,
+2 -2
View File
@@ -1,7 +1,7 @@
import type { NextApiRequest } from "next"; import type { NextApiRequest } from "next";
import { stringify } from "querystring"; import { stringify } from "querystring";
import { WEBAPP_URL } from "@calcom/lib/constants"; import { WEBAPP_URL_FOR_OAUTH } from "@calcom/lib/constants";
import { defaultHandler } from "@calcom/lib/server/defaultHandler"; import { defaultHandler } from "@calcom/lib/server/defaultHandler";
import { defaultResponder } from "@calcom/lib/server/defaultResponder"; import { defaultResponder } from "@calcom/lib/server/defaultResponder";
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
@@ -26,7 +26,7 @@ async function handler(req: NextApiRequest) {
const params = { const params = {
response_type: "code", response_type: "code",
client_id, client_id,
redirect_uri: `${WEBAPP_URL}/api/integrations/zoomvideo/callback`, redirect_uri: `${WEBAPP_URL_FOR_OAUTH}/api/integrations/zoomvideo/callback`,
state, state,
}; };
const query = stringify(params); const query = stringify(params);
+2 -2
View File
@@ -1,6 +1,6 @@
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import { WEBAPP_URL } from "@calcom/lib/constants"; import { WEBAPP_URL_FOR_OAUTH } from "@calcom/lib/constants";
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
@@ -14,7 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const { code } = req.query; const { code } = req.query;
const { client_id, client_secret } = await getZoomAppKeys(); const { client_id, client_secret } = await getZoomAppKeys();
const redirectUri = encodeURI(`${WEBAPP_URL}/api/integrations/zoomvideo/callback`); const redirectUri = encodeURI(`${WEBAPP_URL_FOR_OAUTH}/api/integrations/zoomvideo/callback`);
const authHeader = `Basic ${Buffer.from(`${client_id}:${client_secret}`).toString("base64")}`; const authHeader = `Basic ${Buffer.from(`${client_id}:${client_secret}`).toString("base64")}`;
const result = await fetch( const result = await fetch(
`https://zoom.us/oauth/token?grant_type=authorization_code&code=${code}&redirect_uri=${redirectUri}`, `https://zoom.us/oauth/token?grant_type=authorization_code&code=${code}&redirect_uri=${redirectUri}`,
@@ -8,6 +8,7 @@ import {
CREDENTIAL_SYNC_SECRET_HEADER_NAME, CREDENTIAL_SYNC_SECRET_HEADER_NAME,
} from "@calcom/lib/constants"; } from "@calcom/lib/constants";
import logger from "@calcom/lib/logger"; import logger from "@calcom/lib/logger";
import { getPiiFreeCalendarEvent } from "@calcom/lib/piiFreeData";
import { safeStringify } from "@calcom/lib/safeStringify"; import { safeStringify } from "@calcom/lib/safeStringify";
import prisma from "@calcom/prisma"; import prisma from "@calcom/prisma";
import { Frequency } from "@calcom/prisma/zod-utils"; import { Frequency } from "@calcom/prisma/zod-utils";
@@ -65,12 +66,12 @@ export type ZoomUserSettings = z.infer<typeof zoomUserSettingsSchema>;
export const zoomUserSettingsSchema = z.object({ export const zoomUserSettingsSchema = z.object({
recording: z recording: z
.object({ .object({
auto_recording: z.string(), auto_recording: z.string().nullish(),
}) })
.nullish(), .nullish(),
schedule_meeting: z schedule_meeting: z
.object({ .object({
default_password_for_scheduled_meetings: z.string(), default_password_for_scheduled_meetings: z.string().nullish(),
}) })
.nullish(), .nullish(),
}); });
@@ -185,6 +186,32 @@ const ZoomVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter =>
}; };
}; };
/**
* Zoom is known to return xml response in some cases.
* e.g. Wrong request or some special case of invalid token
*/
const handleZoomResponseJsonParseError = async ({
error,
clonedResponse,
}: {
error: unknown;
clonedResponse: Response;
}) => {
// In some cases, Zoom responds with xml response, so we log the response for debugging
// We need to see why that error occurs exactly and then later we decide if mark the access token and token object unusable or not
log.error(
"Error in JSON parsing Zoom API response",
safeStringify({
error: safeStringify(error),
// Log Raw response body here.
responseBody: await clonedResponse.text(),
status: clonedResponse.status,
})
);
return null;
};
const fetchZoomApi = async (endpoint: string, options?: RequestInit) => { const fetchZoomApi = async (endpoint: string, options?: RequestInit) => {
const auth = new OAuthManager({ const auth = new OAuthManager({
credentialSyncVariables: { credentialSyncVariables: {
@@ -220,9 +247,18 @@ const ZoomVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter =>
}, },
isTokenObjectUnusable: async function (response) { isTokenObjectUnusable: async function (response) {
const myLog = logger.getSubLogger({ prefix: ["zoomvideo:isTokenObjectUnusable"] }); const myLog = logger.getSubLogger({ prefix: ["zoomvideo:isTokenObjectUnusable"] });
myLog.debug(safeStringify({ status: response.status, ok: response.ok })); myLog.info(safeStringify({ status: response.status, ok: response.ok }));
if (!response.ok || (response.status < 200 && response.status >= 300)) { if (!response.ok) {
const responseBody = await response.json(); let responseBody;
const responseToUseInCaseOfError = response.clone();
try {
responseBody = await response.json();
} catch (e) {
return await handleZoomResponseJsonParseError({
error: e,
clonedResponse: responseToUseInCaseOfError,
});
}
myLog.debug(safeStringify({ responseBody })); myLog.debug(safeStringify({ responseBody }));
if (responseBody.error === "invalid_grant") { if (responseBody.error === "invalid_grant") {
@@ -233,11 +269,20 @@ const ZoomVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter =>
}, },
isAccessTokenUnusable: async function (response) { isAccessTokenUnusable: async function (response) {
const myLog = logger.getSubLogger({ prefix: ["zoomvideo:isAccessTokenUnusable"] }); const myLog = logger.getSubLogger({ prefix: ["zoomvideo:isAccessTokenUnusable"] });
myLog.debug(safeStringify({ status: response.status, ok: response.ok })); myLog.info(safeStringify({ status: response.status, ok: response.ok }));
if (!response.ok || (response.status < 200 && response.status >= 300)) { if (!response.ok) {
const responseBody = await response.json(); let responseBody;
const responseToUseInCaseOfError = response.clone();
try {
responseBody = await response.json();
} catch (e) {
return await handleZoomResponseJsonParseError({
error: e,
clonedResponse: responseToUseInCaseOfError,
});
}
myLog.debug(safeStringify({ responseBody })); myLog.debug(safeStringify({ responseBody }));
// 124 is the error code for invalid access token from Zoom API
if (responseBody.code === 124) { if (responseBody.code === 124) {
return { reason: responseBody.message ?? "" }; return { reason: responseBody.message ?? "" };
} }
@@ -284,7 +329,7 @@ const ZoomVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter =>
end: new Date(new Date(meeting.start_time).getTime() + meeting.duration * 60000).toISOString(), end: new Date(new Date(meeting.start_time).getTime() + meeting.duration * 60000).toISOString(),
})); }));
} catch (err) { } catch (err) {
console.error(err); log.error("Failed to get availability", safeStringify(err));
/* Prevents booking failure when Zoom Token is expired */ /* Prevents booking failure when Zoom Token is expired */
return []; return [];
} }
@@ -311,8 +356,10 @@ const ZoomVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter =>
} }
throw new Error(`Failed to create meeting. Response is ${JSON.stringify(result)}`); throw new Error(`Failed to create meeting. Response is ${JSON.stringify(result)}`);
} catch (err) { } catch (err) {
console.error(err); log.error(
log.error("Zoom meeting creation failed", safeStringify({ error: err, event })); "Zoom meeting creation failed",
safeStringify({ error: safeStringify(err), event: getPiiFreeCalendarEvent(event) })
);
/* Prevents meeting creation failure when Zoom Token is expired */ /* Prevents meeting creation failure when Zoom Token is expired */
throw new Error("Unexpected error"); throw new Error("Unexpected error");
} }
@@ -347,7 +394,10 @@ const ZoomVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter =>
url: result.join_url, url: result.join_url,
}; };
} catch (err) { } catch (err) {
log.error("Failed to update meeting", safeStringify(err)); log.error(
"Failed to update meeting",
safeStringify({ error: err, event: getPiiFreeCalendarEvent(event) })
);
return Promise.reject(new Error("Failed to update meeting")); return Promise.reject(new Error("Failed to update meeting"));
} }
}, },
+1 -1
View File
@@ -244,7 +244,7 @@ export default function Booker( props : BookerProps ) {
namespace: string; namespace: string;
}) => { }) => {
return code`${getApiNameForVanillaJsSnippet({ namespace, mainApiName: "Cal" })}("inline", { return code`${getApiNameForVanillaJsSnippet({ namespace, mainApiName: "Cal" })}("inline", {
elementOrSelector:"#my-cal-inline", elementOrSelector:"#my-cal-inline-${namespace}",
config: ${JSON.stringify(previewState.config)}, config: ${JSON.stringify(previewState.config)},
calLink: "${calLink}", calLink: "${calLink}",
}); });
+1 -1
View File
@@ -59,7 +59,7 @@ export const tabs = [
embedType === "inline" embedType === "inline"
? `<div style="width:${getDimension(previewState.inline.width)};height:${getDimension( ? `<div style="width:${getDimension(previewState.inline.width)};height:${getDimension(
previewState.inline.height previewState.inline.height
)};overflow:scroll" id="my-cal-inline"></div>\n` )};overflow:scroll" id="my-cal-inline-${namespace}"></div>\n`
: "" : ""
}<script type="text/javascript"> }<script type="text/javascript">
${embedSnippetString} ${embedSnippetString}
+14 -2
View File
@@ -1,7 +1,17 @@
## 1.0.63
## 1.0.108 ## 1.0.108
## 1.0.112
### Patch Changes
- [#22134](https://github.com/calcom/cal.com/pull/22134) [`3437c43`](https://github.com/calcom/cal.com/commit/3437c4331da892bd77afaea84be2db196543a207) Thanks [@supalarry](https://github.com/supalarry)! - ignore this test log
## 1.0.110
### Patch Changes
- [#22107](https://github.com/calcom/cal.com/pull/22107) [`88a2bd3`](https://github.com/calcom/cal.com/commit/88a2bd3317a2cfaf6b367524e2736445544dbd63) Thanks [@supalarry](https://github.com/supalarry)! - test log can ignore
### Patch Changes ### Patch Changes
- [#22103](https://github.com/calcom/cal.com/pull/22103) [`61274bc`](https://github.com/calcom/cal.com/commit/61274bc7efc67b162d46b59cd75bd376ad515c51) Thanks [@supalarry](https://github.com/supalarry)! - testing changesets - can ignore this - [#22103](https://github.com/calcom/cal.com/pull/22103) [`61274bc`](https://github.com/calcom/cal.com/commit/61274bc7efc67b162d46b59cd75bd376ad515c51) Thanks [@supalarry](https://github.com/supalarry)! - testing changesets - can ignore this
@@ -14,6 +24,8 @@
- [#21864](https://github.com/calcom/cal.com/pull/21864) [`540bf86`](https://github.com/calcom/cal.com/commit/540bf868b5b60a98d9d3aeb565e2089f15c3dfd3) Thanks [@supalarry](https://github.com/supalarry)! - fix saving event type settings - [#21864](https://github.com/calcom/cal.com/pull/21864) [`540bf86`](https://github.com/calcom/cal.com/commit/540bf868b5b60a98d9d3aeb565e2089f15c3dfd3) Thanks [@supalarry](https://github.com/supalarry)! - fix saving event type settings
## 1.0.63
1. 💥 BREAKING - `useGetBooking` hook has been renamed to `useBooking` hook and `useGetBookings` hook to `useBookings` and the data returned has different 1. 💥 BREAKING - `useGetBooking` hook has been renamed to `useBooking` hook and `useGetBookings` hook to `useBookings` and the data returned has different
structure. Here is example response from `useBooking`: structure. Here is example response from `useBooking`:
+1 -1
View File
@@ -13,6 +13,6 @@ Currently supports React 18, React 19, Next 14 and Next 15.
3. Some of the versions are suffixed e.g. `1.0.102-framer` and are intended for isolated use cases, so you most probably 3. Some of the versions are suffixed e.g. `1.0.102-framer` and are intended for isolated use cases, so you most probably
want to use version without any suffix e.g. `1.0.103`. want to use version without any suffix e.g. `1.0.103`.
### Documentation ### Documentation
Documentation on how to get started with platform solution is [here](https://cal.com/docs/platform/quickstart) and list of atoms can be viewed Documentation on how to get started with platform solution is [here](https://cal.com/docs/platform/quickstart) and list of atoms can be viewed
[here](https://cal.com/docs/platform/atoms/cal-provider) [here](https://cal.com/docs/platform/atoms/cal-provider)
+3 -3
View File
@@ -4,11 +4,11 @@
"type": "module", "type": "module",
"description": "Customizable UI components to integrate scheduling into your product.", "description": "Customizable UI components to integrate scheduling into your product.",
"authors": "Cal.com, Inc.", "authors": "Cal.com, Inc.",
"version": "1.0.108", "version": "1.0.112",
"scripts": { "scripts": {
"dev": "yarn vite build --watch & npx tailwindcss -i ./globals.css -o ./globals.min.css --postcss --minify --watch", "dev": "yarn vite build --watch & npx tailwindcss -i ./globals.css -o ./globals.min.css --postcss --minify --watch",
"build": "NODE_OPTIONS='--max_old_space_size=16384' rm -rf dist && yarn vite build && npx tailwindcss -i ./globals.css -o ./globals.min.css --postcss --minify && mkdir ./dist/packages/prisma-client && cp -rf ../../../node_modules/.prisma/client/*.d.ts ./dist/packages/prisma-client", "build": "NODE_OPTIONS='--max_old_space_size=12288' rm -rf dist && yarn vite build && npx tailwindcss -i ./globals.css -o ./globals.min.css --postcss --minify && mkdir ./dist/packages/prisma-client && cp -rf ../../../node_modules/.prisma/client/*.d.ts ./dist/packages/prisma-client",
"publish": "yarn build && npm publish --access public", "publish-npm": "yarn build && npm publish --access public",
"test": "jest" "test": "jest"
}, },
"devDependencies": { "devDependencies": {
+3
View File
@@ -68,6 +68,9 @@
"cache": false, "cache": false,
"dependsOn": [] "dependsOn": []
}, },
"@calcom/atoms#build": {
"outputs": ["dist/**", "globals.min.css"]
},
"@calcom/api-v2#build": { "@calcom/api-v2#build": {
"dependsOn": ["^build"], "dependsOn": ["^build"],
"env": [ "env": [