diff --git a/README.md b/README.md index dd0fd9a1b5..0766b12f99 100644 --- a/README.md +++ b/README.md @@ -460,6 +460,10 @@ following 9. Click the "Save" button at the bottom footer. 10. You're good to go. Now you can see any booking in Cal.com created as a meeting in HubSpot for your contacts. +### Obtaining Webex Client ID and Secret + +[See Webex Readme](./packages/app-store/webex/) + ### Obtaining ZohoCRM Client ID and Secret 1. Open [Zoho API Console](https://api-console.zoho.com/) and sign into your account, or create a new one. diff --git a/packages/app-store/apps.keys-schemas.generated.ts b/packages/app-store/apps.keys-schemas.generated.ts index e02d3d6e25..dff887f123 100644 --- a/packages/app-store/apps.keys-schemas.generated.ts +++ b/packages/app-store/apps.keys-schemas.generated.ts @@ -24,6 +24,7 @@ import { appKeysSchema as tandemvideo_zod_ts } from "./tandemvideo/zod"; import { appKeysSchema as booking_pages_tag_zod_ts } from "./templates/booking-pages-tag/zod"; import { appKeysSchema as event_type_app_card_zod_ts } from "./templates/event-type-app-card/zod"; import { appKeysSchema as vital_zod_ts } from "./vital/zod"; +import { appKeysSchema as webex_zod_ts } from "./webex/zod"; import { appKeysSchema as wordpress_zod_ts } from "./wordpress/zod"; import { appKeysSchema as zapier_zod_ts } from "./zapier/zod"; import { appKeysSchema as zoho_bigin_zod_ts } from "./zoho-bigin/zod"; @@ -53,6 +54,7 @@ export const appKeysSchemas = { "booking-pages-tag": booking_pages_tag_zod_ts, "event-type-app-card": event_type_app_card_zod_ts, vital: vital_zod_ts, + webex: webex_zod_ts, wordpress: wordpress_zod_ts, zapier: zapier_zod_ts, "zoho-bigin": zoho_bigin_zod_ts, diff --git a/packages/app-store/apps.metadata.generated.ts b/packages/app-store/apps.metadata.generated.ts index bb402e60a3..db829a26f2 100644 --- a/packages/app-store/apps.metadata.generated.ts +++ b/packages/app-store/apps.metadata.generated.ts @@ -55,6 +55,7 @@ import typeform_config_json from "./typeform/config.json"; import vimcal_config_json from "./vimcal/config.json"; import { metadata as vital__metadata_ts } from "./vital/_metadata"; import weather_in_your_calendar_config_json from "./weather_in_your_calendar/config.json"; +import webex_config_json from "./webex/config.json"; import whatsapp_config_json from "./whatsapp/config.json"; import whereby_config_json from "./whereby/config.json"; import { metadata as wipemycalother__metadata_ts } from "./wipemycalother/_metadata"; @@ -118,6 +119,7 @@ export const appStoreMetadata = { vimcal: vimcal_config_json, vital: vital__metadata_ts, weather_in_your_calendar: weather_in_your_calendar_config_json, + webex: webex_config_json, whatsapp: whatsapp_config_json, whereby: whereby_config_json, wipemycalother: wipemycalother__metadata_ts, diff --git a/packages/app-store/apps.schemas.generated.ts b/packages/app-store/apps.schemas.generated.ts index 624e59935b..0caa880e9b 100644 --- a/packages/app-store/apps.schemas.generated.ts +++ b/packages/app-store/apps.schemas.generated.ts @@ -24,6 +24,7 @@ import { appDataSchema as tandemvideo_zod_ts } from "./tandemvideo/zod"; import { appDataSchema as booking_pages_tag_zod_ts } from "./templates/booking-pages-tag/zod"; import { appDataSchema as event_type_app_card_zod_ts } from "./templates/event-type-app-card/zod"; import { appDataSchema as vital_zod_ts } from "./vital/zod"; +import { appDataSchema as webex_zod_ts } from "./webex/zod"; import { appDataSchema as wordpress_zod_ts } from "./wordpress/zod"; import { appDataSchema as zapier_zod_ts } from "./zapier/zod"; import { appDataSchema as zoho_bigin_zod_ts } from "./zoho-bigin/zod"; @@ -53,6 +54,7 @@ export const appDataSchemas = { "booking-pages-tag": booking_pages_tag_zod_ts, "event-type-app-card": event_type_app_card_zod_ts, vital: vital_zod_ts, + webex: webex_zod_ts, wordpress: wordpress_zod_ts, zapier: zapier_zod_ts, "zoho-bigin": zoho_bigin_zod_ts, diff --git a/packages/app-store/apps.server.generated.ts b/packages/app-store/apps.server.generated.ts index 7b35ab5809..cabd37632f 100644 --- a/packages/app-store/apps.server.generated.ts +++ b/packages/app-store/apps.server.generated.ts @@ -55,6 +55,7 @@ export const apiHandlers = { vimcal: import("./vimcal/api"), vital: import("./vital/api"), weather_in_your_calendar: import("./weather_in_your_calendar/api"), + webex: import("./webex/api"), whatsapp: import("./whatsapp/api"), whereby: import("./whereby/api"), wipemycalother: import("./wipemycalother/api"), diff --git a/packages/app-store/index.ts b/packages/app-store/index.ts index cf5271ecba..6007299922 100644 --- a/packages/app-store/index.ts +++ b/packages/app-store/index.ts @@ -21,6 +21,7 @@ const appStore = { vital: () => import("./vital"), zoomvideo: () => import("./zoomvideo"), wipemycalother: () => import("./wipemycalother"), + webexvideo: () => import("./webex"), giphy: () => import("./giphy"), zapier: () => import("./zapier"), exchange2013calendar: () => import("./exchange2013calendar"), diff --git a/packages/app-store/webex/DESCRIPTION.md b/packages/app-store/webex/DESCRIPTION.md new file mode 100644 index 0000000000..7a8120560e --- /dev/null +++ b/packages/app-store/webex/DESCRIPTION.md @@ -0,0 +1,9 @@ +--- +items: + - 1.jpeg + - 2.jpeg + - 3.jpeg + - 4.jpeg +--- + +{DESCRIPTION} diff --git a/packages/app-store/webex/README.md b/packages/app-store/webex/README.md new file mode 100644 index 0000000000..f72dc04a23 --- /dev/null +++ b/packages/app-store/webex/README.md @@ -0,0 +1,14 @@ +### Obtaining Webex Client ID and Secret + +1. Create a [Webex](https://www.webex.com/) acount, if you don't already have one. +2. Go to [Webex for Developers](https://developer.webex.com/) and sign into to your Webex account. (Note: If you're creating a new account, create it on [Webex](https://www.webex.com/), not on [Webex for Developers](https://developer.webex.com/)) +3. On the upper right, click the profile icon and go to ["My Webex Apps"](https://developer.webex.com/my-apps) +4. Click on "Create a New App" and select ["Integration"](https://developer.webex.com/my-apps/new/integration) +5. Choose "No" for "Will this use a mobile SDK?" +6. Give your app a name. +7. Upload an icon or choose one of the default icons. +8. Give your app a short description. +9. Set the Redirect URI as `/api/integrations/webex/callback` replacing Cal.com URL with the URI at which your application runs. +10. Select the following scopes: "meeting:schedules_read", "meeting:schedules_write". +11. Click "Add Integration". +12. Copy the Client ID and Client Secret and add these while enabling the app through Settings -> Admin -> Apps interface diff --git a/packages/app-store/webex/api/add.ts b/packages/app-store/webex/api/add.ts new file mode 100644 index 0000000000..3bc0f8a806 --- /dev/null +++ b/packages/app-store/webex/api/add.ts @@ -0,0 +1,39 @@ +import type { NextApiRequest } from "next"; +import { stringify } from "querystring"; + +import { WEBAPP_URL } from "@calcom/lib/constants"; +import { defaultHandler, defaultResponder } from "@calcom/lib/server"; +import prisma from "@calcom/prisma"; + +import config from "../config.json"; +import { getWebexAppKeys } from "../lib/getWebexAppKeys"; + +async function handler(req: NextApiRequest) { + // Get user + await prisma.user.findFirstOrThrow({ + where: { + id: req.session?.user?.id, + }, + select: { + id: true, + }, + }); + + const { client_id } = await getWebexAppKeys(); + + /** @link https://developer.webex.com/docs/integrations#requesting-permission */ + const params = { + response_type: "code", + client_id, + redirect_uri: `${WEBAPP_URL}/api/integrations/${config.slug}/callback`, + scope: "spark:kms meeting:schedules_read meeting:schedules_write", //should be "A space-separated list of scopes being requested by your integration" + state: "", + }; + const query = stringify(params).replaceAll("+", "%20"); + const url = `https://webexapis.com/v1/authorize?${query}`; + return { url }; +} + +export default defaultHandler({ + GET: Promise.resolve({ default: defaultResponder(handler) }), +}); diff --git a/packages/app-store/webex/api/callback.ts b/packages/app-store/webex/api/callback.ts new file mode 100644 index 0000000000..f5ca865423 --- /dev/null +++ b/packages/app-store/webex/api/callback.ts @@ -0,0 +1,99 @@ +import type { NextApiRequest, NextApiResponse } from "next"; + +import { WEBAPP_URL } from "@calcom/lib/constants"; +import prisma from "@calcom/prisma"; + +import getInstalledAppPath from "../../_utils/getInstalledAppPath"; +import config from "../config.json"; +import { getWebexAppKeys } from "../lib/getWebexAppKeys"; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const { code } = req.query; + const { client_id, client_secret } = await getWebexAppKeys(); + + /** @link https://developer.webex.com/docs/integrations#getting-an-access-token **/ + + const redirectUri = encodeURI(`${WEBAPP_URL}/api/integrations/${config.slug}/callback`); + const authHeader = "Basic " + Buffer.from(client_id + ":" + client_secret).toString("base64"); + const result = await fetch( + "https://webexapis.com/v1/access_token?grant_type=authorization_code&client_id" + + client_id + + "&client_secret=" + + client_secret + + "&code=" + + code + + "&redirect_uri=" + + redirectUri, + { + method: "POST", + headers: { + Authorization: authHeader, + "Content-Type": "application/x-www-form-urlencoded", + }, + } + ); + + if (result.status !== 200) { + let errorMessage = "Something is wrong with Webex API"; + try { + const responseBody = await result.json(); + errorMessage = responseBody.error; + } catch (e) {} + + res.status(400).json({ message: errorMessage }); + return; + } + + const responseBody = await result.json(); + + if (responseBody.error) { + res.status(400).json({ message: responseBody.error }); + return; + } + + responseBody.expiry_date = Math.round(Date.now() + responseBody.expires_in * 1000); + delete responseBody.expires_in; + + const userId = req.session?.user.id; + if (!userId) { + return res.status(404).json({ message: "No user found" }); + } + /** + * With this we take care of no duplicate webex key for a single user + * when creating a video room we only do findFirst so the if they have more than 1 + * others get ignored + * */ + const existingCredentialWebexVideo = await prisma.credential.findMany({ + select: { + id: true, + }, + where: { + type: config.type, + userId: req.session?.user.id, + appId: config.slug, + }, + }); + + // Making sure we only delete webex_video + const credentialIdsToDelete = existingCredentialWebexVideo.map((item) => item.id); + if (credentialIdsToDelete.length > 0) { + await prisma.credential.deleteMany({ where: { id: { in: credentialIdsToDelete }, userId } }); + } + + await prisma.user.update({ + where: { + id: req.session?.user.id, + }, + data: { + credentials: { + create: { + type: config.type, + key: responseBody, + appId: config.slug, + }, + }, + }, + }); + + res.redirect(getInstalledAppPath({ variant: config.variant, slug: config.slug })); +} diff --git a/packages/app-store/webex/api/index.ts b/packages/app-store/webex/api/index.ts new file mode 100644 index 0000000000..eb12c1b4ed --- /dev/null +++ b/packages/app-store/webex/api/index.ts @@ -0,0 +1,2 @@ +export { default as add } from "./add"; +export { default as callback } from "./callback"; diff --git a/packages/app-store/webex/config.json b/packages/app-store/webex/config.json new file mode 100644 index 0000000000..c2737bad52 --- /dev/null +++ b/packages/app-store/webex/config.json @@ -0,0 +1,25 @@ +{ + "/*": "Don't modify slug - If required, do it using cli edit command", + "name": "Webex", + "title": "Webex", + "slug": "webex", + "type": "webex_video", + "imageSrc": "/icon.ico", + "logo": "/icon.ico", + "url": "https://cal.com/apps/webex", + "variant": "conferencing", + "categories": ["video"], + "publisher": "Cal.com, Inc.", + "email": "support@cal.com", + "description": "Create meetings with Cisco Webex", + "appData": { + "location": { + "linkType": "dynamic", + "type": "integrations:webex_video", + "label": "Webex" + } + }, + "isTemplate": false, + "__createdUsingCli": true, + "__template": "basic" +} diff --git a/packages/app-store/webex/index.ts b/packages/app-store/webex/index.ts new file mode 100644 index 0000000000..e2e9d7b029 --- /dev/null +++ b/packages/app-store/webex/index.ts @@ -0,0 +1,2 @@ +export * as api from "./api"; +export * as lib from "./lib"; diff --git a/packages/app-store/webex/lib/VideoApiAdapter.ts b/packages/app-store/webex/lib/VideoApiAdapter.ts new file mode 100644 index 0000000000..f7f4729114 --- /dev/null +++ b/packages/app-store/webex/lib/VideoApiAdapter.ts @@ -0,0 +1,304 @@ +import { z } from "zod"; + +import dayjs from "@calcom/dayjs"; +import prisma from "@calcom/prisma"; +import type { Credential } from "@calcom/prisma/client"; +import type { CalendarEvent } from "@calcom/types/Calendar"; +import type { CredentialPayload } from "@calcom/types/Credential"; +import type { PartialReference } from "@calcom/types/EventManager"; +import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter"; + +import { getWebexAppKeys } from "./getWebexAppKeys"; + +/** @link https://developer.webex.com/docs/meetings **/ +const webexEventResultSchema = z.object({ + id: z.string(), + webLink: z.string(), + siteUrl: z.string(), + password: z.string().optional().default(""), +}); + +export type WebexEventResult = z.infer; + +/** @link https://developer.webex.com/docs/api/v1/meetings/create-a-meeting */ +export const webexMeetingSchema = z.object({ + start: z.date(), + end: z.date(), +}); + +/** @link https://developer.webex.com/docs/api/v1/meetings/list-meetings */ +export const webexMeetingsSchema = z.object({ + items: z.array(webexMeetingSchema), +}); + +/** @link https://developer.webex.com/docs/integrations#getting-an-access-token */ +const webexTokenSchema = z.object({ + scope: z.literal("spark:kms meeting:schedules_read meeting:schedules_write"), + token_type: z.literal("Bearer"), + access_token: z.string(), + expires_in: z.number().optional(), + refresh_token: z.string(), + refresh_token_expires_in: z.number(), + expiry_date: z.number(), +}); +type WebexToken = z.infer; +const isTokenValid = (token: WebexToken) => token.expiry_date < Date.now(); + +/** @link https://developer.webex.com/docs/integrations#using-the-refresh-token */ +const webexRefreshedTokenSchema = z.object({ + scope: z.literal("spark:kms meeting:schedules_read meeting:schedules_write"), + token_type: z.literal("Bearer"), + access_token: z.string(), + expires_in: z.number().optional(), + refresh_token: z.string(), + refresh_token_expires_in: z.number(), +}); + +const webexAuth = (credential: CredentialPayload) => { + const refreshAccessToken = async (refreshToken: string) => { + const { client_id, client_secret } = await getWebexAppKeys(); + + const response = await fetch("https://webexapis.com/v1/access_token", { + method: "POST", + headers: { + "Content-type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ + grant_type: "refresh_token", + client_id: client_id, + client_secret: client_secret, + refresh_token: refreshToken, + }), + }); + + const responseBody = await handleWebexResponse(response, credential.id); + + if (responseBody.error) { + if (responseBody.error === "invalid_grant") { + return Promise.reject(new Error("Invalid grant for Cal.com webex app")); + } + } + // We check the if the new credentials matches the expected response structure + const parsedToken = webexRefreshedTokenSchema.safeParse(responseBody); + if (!parsedToken.success) { + return Promise.reject(new Error("Invalid refreshed tokens were returned")); + } + const newTokens = parsedToken.data; + const oldCredential = await prisma.credential.findUniqueOrThrow({ where: { id: credential.id } }); + const parsedKey = webexTokenSchema.safeParse(oldCredential.key); + if (!parsedKey.success) { + return Promise.reject(new Error("Invalid credentials were saved in the DB")); + } + + const key = parsedKey.data; + key.access_token = newTokens.access_token; + key.refresh_token = newTokens.refresh_token; + // set expiry date as offset from current time. + if (newTokens.expires_in) { + key.expiry_date = Math.round(Date.now() + newTokens.expires_in * 1000); + } + // Store new tokens in database. + await prisma.credential.update({ where: { id: credential.id }, data: { key } }); + return newTokens.access_token; + }; + return { + getToken: async () => { + let credentialKey: WebexToken | null = null; + try { + credentialKey = webexTokenSchema.parse(credential.key); + } catch (error) { + return Promise.reject("Webex credential keys parsing error"); + } + + return !isTokenValid(credentialKey) + ? Promise.resolve(credentialKey.access_token) + : refreshAccessToken(credentialKey.refresh_token); + }, + }; +}; + +const WebexVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter => { + //TODO implement translateEvent for recurring events + const translateEvent = (event: CalendarEvent) => { + //To convert the Cal's CalendarEvent type to a webex meeting type + /** @link https://developer.webex.com/docs/api/v1/meetings/create-a-meeting */ + //Required params - title, start, end + + return { + title: event.title, + start: dayjs(event.startTime).utc().format(), + end: dayjs(event.endTime).utc().format(), + recurrence: event.recurrence, //Follows RFC 2445 https://www.ietf.org/rfc/rfc2445.txt, TODO check if needs conversion + // timezone: event.organizer.timeZone, // Comment this out for now + agenda: event.description, + enableJoinBeforeHost: true, //this is true in zoom's api, do we need it here? + invitees: event.attendees.map((attendee) => ({ + email: attendee.email, + })), + sendEmail: true, + }; + }; + + const fetchWebexApi = async (endpoint: string, options?: RequestInit) => { + const auth = webexAuth(credential); + const accessToken = await auth.getToken(); + console.log("result of accessToken in fetchWebexApi", accessToken); + console.log("createMeeting options in fetchWebexApi", options); + const response = await fetch(`https://webexapis.com/v1/${endpoint}`, { + method: "GET", + ...options, + headers: { + Authorization: "Bearer " + accessToken, + ...options?.headers, + }, + }); + const responseBody = await handleWebexResponse(response, credential.id); + return responseBody; + }; + + return { + getAvailability: async () => { + try { + const responseBody = await fetchWebexApi("meetings"); + + const data = webexMeetingsSchema.passthrough().parse(responseBody); + return data.items.map((meeting) => ({ + start: meeting.start, + end: meeting.end, + })); + } catch (err) { + console.error(err); + + return []; + } + }, + createMeeting: async (event: CalendarEvent): Promise => { + /** @link https://developer.webex.com/docs/api/v1/meetings/create-a-meeting */ + try { + console.log("Creating meeting", event); + console.log("meting body", translateEvent(event)); + console.log("request body in createMeeting", JSON.stringify(translateEvent(event))); + const response = await fetchWebexApi("meetings", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(translateEvent(event)), + }); + console.log("Webex create meeting response", response); + if (response.error) { + if (response.error === "invalid_grant") { + await invalidateCredential(credential.id); + return Promise.reject(new Error("Invalid grant for Cal.com webex app")); + } + } + + const result = webexEventResultSchema.parse(response); + if (result.id && result.webLink) { + return { + type: "webex_video", + id: result.id.toString(), + password: result.password || "", + url: result.webLink, + }; + } + throw new Error("Failed to create meeting. Response is " + JSON.stringify(result)); + } catch (err) { + console.error(err); + throw new Error("Unexpected error"); + } + }, + deleteMeeting: async (uid: string): Promise => { + /** @link https://developer.webex.com/docs/api/v1/meetings/delete-a-meeting */ + try { + const response = await fetchWebexApi(`meetings/${uid}`, { + method: "DELETE", + }); + console.log("Webex delete meeting response", response); + if (response.error) { + if (response.error === "invalid_grant") { + await invalidateCredential(credential.id); + return Promise.reject(new Error("Invalid grant for Cal.com webex app")); + } + } + return Promise.resolve(); + } catch (err) { + return Promise.reject(new Error("Failed to delete meeting")); + } + }, + updateMeeting: async (bookingRef: PartialReference, event: CalendarEvent): Promise => { + /** @link https://developer.webex.com/docs/api/v1/meetings/update-a-meeting */ + try { + const response = await fetchWebexApi(`meetings/${bookingRef.uid}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(translateEvent(event)), + }); + if (response.error) { + if (response.error === "invalid_grant") { + await invalidateCredential(credential.id); + return Promise.reject(new Error("Invalid grant for Cal.com webex app")); + } + } + + const result = webexEventResultSchema.parse(response); + if (result.id && result.webLink) { + return { + type: "webex_video", + id: bookingRef.meetingId as string, + password: result.password || "", + url: result.webLink, + }; + } + throw new Error("Failed to create meeting. Response is " + JSON.stringify(result)); + } catch (err) { + console.error(err); + throw new Error("Unexpected error"); + } + }, + }; +}; + +const handleWebexResponse = async (response: Response, credentialId: Credential["id"]) => { + let _response = response.clone(); + const responseClone = response.clone(); + if (_response.headers.get("content-encoding") === "gzip") { + const responseString = await response.text(); + _response = JSON.parse(responseString); + } + if (!response.ok || (response.status < 200 && response.status >= 300)) { + const responseBody = await _response.json(); + + if ((response && response.status === 124) || responseBody.error === "invalid_grant") { + await invalidateCredential(credentialId); + } + throw Error(response.statusText); + } + // handle 204 response code with empty response (causes crash otherwise as "" is invalid JSON) + if (response.status === 204) { + return; + } + return responseClone.json(); +}; + +const invalidateCredential = async (credentialId: Credential["id"]) => { + const credential = await prisma.credential.findUnique({ + where: { + id: credentialId, + }, + }); + + if (credential) { + await prisma.credential.update({ + where: { + id: credentialId, + }, + data: { + invalid: true, + }, + }); + } +}; +export default WebexVideoApiAdapter; diff --git a/packages/app-store/webex/lib/getWebexAppKeys.ts b/packages/app-store/webex/lib/getWebexAppKeys.ts new file mode 100644 index 0000000000..c76fd6df9f --- /dev/null +++ b/packages/app-store/webex/lib/getWebexAppKeys.ts @@ -0,0 +1,13 @@ +import { z } from "zod"; + +import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug"; + +const webexAppKeysSchema = z.object({ + client_id: z.string(), + client_secret: z.string(), +}); + +export const getWebexAppKeys = async () => { + const appKeys = await getAppKeysFromSlug("webex"); + return webexAppKeysSchema.parse(appKeys); +}; diff --git a/packages/app-store/webex/lib/index.ts b/packages/app-store/webex/lib/index.ts new file mode 100644 index 0000000000..8dae91448a --- /dev/null +++ b/packages/app-store/webex/lib/index.ts @@ -0,0 +1,2 @@ +export { getWebexAppKeys } from "./getWebexAppKeys"; +export { default as VideoApiAdapter } from "./VideoApiAdapter"; diff --git a/packages/app-store/webex/package.json b/packages/app-store/webex/package.json new file mode 100644 index 0000000000..d441e53448 --- /dev/null +++ b/packages/app-store/webex/package.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "private": true, + "name": "@calcom/webex", + "version": "0.0.0", + "main": "./index.ts", + "dependencies": { + "@calcom/lib": "*" + }, + "devDependencies": { + "@calcom/types": "*" + }, + "description": "Create meetings with Cisco Webex" +} diff --git a/packages/app-store/webex/static/1.jpeg b/packages/app-store/webex/static/1.jpeg new file mode 100644 index 0000000000..14947282da Binary files /dev/null and b/packages/app-store/webex/static/1.jpeg differ diff --git a/packages/app-store/webex/static/2.jpeg b/packages/app-store/webex/static/2.jpeg new file mode 100644 index 0000000000..f26c664426 Binary files /dev/null and b/packages/app-store/webex/static/2.jpeg differ diff --git a/packages/app-store/webex/static/3.jpeg b/packages/app-store/webex/static/3.jpeg new file mode 100644 index 0000000000..cc190ed6a3 Binary files /dev/null and b/packages/app-store/webex/static/3.jpeg differ diff --git a/packages/app-store/webex/static/4.jpeg b/packages/app-store/webex/static/4.jpeg new file mode 100644 index 0000000000..6242781fde Binary files /dev/null and b/packages/app-store/webex/static/4.jpeg differ diff --git a/packages/app-store/webex/static/icon.ico b/packages/app-store/webex/static/icon.ico new file mode 100644 index 0000000000..2fd572c67e Binary files /dev/null and b/packages/app-store/webex/static/icon.ico differ diff --git a/packages/app-store/webex/zod.ts b/packages/app-store/webex/zod.ts new file mode 100644 index 0000000000..8523421dce --- /dev/null +++ b/packages/app-store/webex/zod.ts @@ -0,0 +1,8 @@ +import { z } from "zod"; + +export const appDataSchema = z.object({}); + +export const appKeysSchema = z.object({ + client_id: z.string().min(64), + client_secret: z.string().min(64), +}); diff --git a/packages/app-store/zoomvideo/lib/VideoApiAdapter.ts b/packages/app-store/zoomvideo/lib/VideoApiAdapter.ts index 2b12f84969..ef227596c7 100644 --- a/packages/app-store/zoomvideo/lib/VideoApiAdapter.ts +++ b/packages/app-store/zoomvideo/lib/VideoApiAdapter.ts @@ -20,7 +20,7 @@ const zoomEventResultSchema = z.object({ export type ZoomEventResult = z.infer; -// @TODO: add link to the docs +/** @link https://marketplace.zoom.us/docs/api-reference/zoom-api/methods/#operation/meetings */ export const zoomMeetingsSchema = z.object({ next_page_token: z.string(), page_count: z.number(), diff --git a/packages/core/videoClient.ts b/packages/core/videoClient.ts index 91c48dff89..244354967d 100644 --- a/packages/core/videoClient.ts +++ b/packages/core/videoClient.ts @@ -142,6 +142,7 @@ const updateMeeting = async ( const deleteMeeting = async (credential: CredentialPayload, uid: string): Promise => { if (credential) { const videoAdapter = (await getVideoAdapters([credential]))[0]; + logger.debug("videoAdapter inside deleteMeeting", { credential, uid }); // There are certain video apps with no video adapter defined. e.g. riverby,whereby if (videoAdapter) { return videoAdapter.deleteMeeting(uid); diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index cc345d433f..5e631cc20f 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -18,6 +18,7 @@ import type { EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; import sendPayload from "@calcom/features/webhooks/lib/sendPayload"; import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib"; import { HttpError } from "@calcom/lib/http-error"; +import logger from "@calcom/lib/logger"; import { handleRefundError } from "@calcom/lib/payment/handleRefundError"; import { getTranslation } from "@calcom/lib/server/i18n"; import prisma, { bookingMinimalSelect } from "@calcom/prisma"; @@ -503,6 +504,7 @@ async function handler(req: CustomRequest) { ); if (videoCredential) { + logger.debug("videoCredential inside cancel booking handler", videoCredential); apiDeletes.push(deleteMeeting(videoCredential, uid)); } } diff --git a/packages/prisma/enums/index.ts b/packages/prisma/enums/index.ts new file mode 100644 index 0000000000..a7357f06b0 --- /dev/null +++ b/packages/prisma/enums/index.ts @@ -0,0 +1,148 @@ +// This file was generated by a custom prisma generator, do not edit manually. +export const SchedulingType = { + ROUND_ROBIN: "ROUND_ROBIN", + COLLECTIVE: "COLLECTIVE", + MANAGED: "MANAGED", +} as const; + +export type SchedulingType = (typeof SchedulingType)[keyof typeof SchedulingType]; + +export const PeriodType = { + UNLIMITED: "UNLIMITED", + ROLLING: "ROLLING", + RANGE: "RANGE", +} as const; + +export type PeriodType = (typeof PeriodType)[keyof typeof PeriodType]; + +export const IdentityProvider = { + CAL: "CAL", + GOOGLE: "GOOGLE", + SAML: "SAML", +} as const; + +export type IdentityProvider = (typeof IdentityProvider)[keyof typeof IdentityProvider]; + +export const UserPermissionRole = { + USER: "USER", + ADMIN: "ADMIN", +} as const; + +export type UserPermissionRole = (typeof UserPermissionRole)[keyof typeof UserPermissionRole]; + +export const MembershipRole = { + MEMBER: "MEMBER", + ADMIN: "ADMIN", + OWNER: "OWNER", +} as const; + +export type MembershipRole = (typeof MembershipRole)[keyof typeof MembershipRole]; + +export const BookingStatus = { + CANCELLED: "CANCELLED", + ACCEPTED: "ACCEPTED", + REJECTED: "REJECTED", + PENDING: "PENDING", +} as const; + +export type BookingStatus = (typeof BookingStatus)[keyof typeof BookingStatus]; + +export const EventTypeCustomInputType = { + TEXT: "TEXT", + TEXTLONG: "TEXTLONG", + NUMBER: "NUMBER", + BOOL: "BOOL", + RADIO: "RADIO", + PHONE: "PHONE", +} as const; + +export type EventTypeCustomInputType = + (typeof EventTypeCustomInputType)[keyof typeof EventTypeCustomInputType]; + +export const ReminderType = { + PENDING_BOOKING_CONFIRMATION: "PENDING_BOOKING_CONFIRMATION", +} as const; + +export type ReminderType = (typeof ReminderType)[keyof typeof ReminderType]; + +export const PaymentOption = { + ON_BOOKING: "ON_BOOKING", + HOLD: "HOLD", +} as const; + +export type PaymentOption = (typeof PaymentOption)[keyof typeof PaymentOption]; + +export const WebhookTriggerEvents = { + BOOKING_CREATED: "BOOKING_CREATED", + BOOKING_RESCHEDULED: "BOOKING_RESCHEDULED", + BOOKING_CANCELLED: "BOOKING_CANCELLED", + FORM_SUBMITTED: "FORM_SUBMITTED", + MEETING_ENDED: "MEETING_ENDED", +} as const; + +export type WebhookTriggerEvents = (typeof WebhookTriggerEvents)[keyof typeof WebhookTriggerEvents]; + +export const AppCategories = { + calendar: "calendar", + messaging: "messaging", + other: "other", + payment: "payment", + video: "video", + web3: "web3", + automation: "automation", + analytics: "analytics", +} as const; + +export type AppCategories = (typeof AppCategories)[keyof typeof AppCategories]; + +export const WorkflowTriggerEvents = { + BEFORE_EVENT: "BEFORE_EVENT", + EVENT_CANCELLED: "EVENT_CANCELLED", + NEW_EVENT: "NEW_EVENT", + AFTER_EVENT: "AFTER_EVENT", + RESCHEDULE_EVENT: "RESCHEDULE_EVENT", +} as const; + +export type WorkflowTriggerEvents = (typeof WorkflowTriggerEvents)[keyof typeof WorkflowTriggerEvents]; + +export const WorkflowActions = { + EMAIL_HOST: "EMAIL_HOST", + EMAIL_ATTENDEE: "EMAIL_ATTENDEE", + SMS_ATTENDEE: "SMS_ATTENDEE", + SMS_NUMBER: "SMS_NUMBER", + EMAIL_ADDRESS: "EMAIL_ADDRESS", +} as const; + +export type WorkflowActions = (typeof WorkflowActions)[keyof typeof WorkflowActions]; + +export const TimeUnit = { + DAY: "DAY", + HOUR: "HOUR", + MINUTE: "MINUTE", +} as const; + +export type TimeUnit = (typeof TimeUnit)[keyof typeof TimeUnit]; + +export const WorkflowTemplates = { + REMINDER: "REMINDER", + CUSTOM: "CUSTOM", +} as const; + +export type WorkflowTemplates = (typeof WorkflowTemplates)[keyof typeof WorkflowTemplates]; + +export const WorkflowMethods = { + EMAIL: "EMAIL", + SMS: "SMS", +} as const; + +export type WorkflowMethods = (typeof WorkflowMethods)[keyof typeof WorkflowMethods]; + +export const FeatureType = { + RELEASE: "RELEASE", + EXPERIMENT: "EXPERIMENT", + OPERATIONAL: "OPERATIONAL", + KILL_SWITCH: "KILL_SWITCH", + PERMISSION: "PERMISSION", +} as const; + +export type FeatureType = (typeof FeatureType)[keyof typeof FeatureType]; diff --git a/packages/prisma/seed-app-store.config.json b/packages/prisma/seed-app-store.config.json index f8bb178003..f3b6f15dd5 100644 --- a/packages/prisma/seed-app-store.config.json +++ b/packages/prisma/seed-app-store.config.json @@ -212,6 +212,13 @@ "type": "zohocrm_other_calendar", "isTemplate": false }, + { + "dirName": "webex", + "categories": ["video"], + "slug": "webex", + "type": "webex_video", + "isTemplate": false + }, { "dirName": "cron", "categories": ["calendar"], diff --git a/packages/trpc/server/createNextApiHandler.ts b/packages/trpc/server/createNextApiHandler.ts index 05ac119017..53202e3f93 100644 --- a/packages/trpc/server/createNextApiHandler.ts +++ b/packages/trpc/server/createNextApiHandler.ts @@ -1,12 +1,14 @@ import { z } from "zod"; -import type { AnyRouter } from "@trpc/server"; + import * as trpcNext from "@calcom/trpc/server/adapters/next"; import { createContext as createTrpcContext } from "@calcom/trpc/server/createContext"; +import type { AnyRouter } from "@trpc/server"; + /** * Creates an API handler executed by Next.js. */ -export function createNextApiHandler(router: AnyRouter, isPublic: boolean = false) { +export function createNextApiHandler(router: AnyRouter, isPublic = false) { return trpcNext.createNextApiHandler({ router, /** @@ -57,11 +59,11 @@ export function createNextApiHandler(router: AnyRouter, isPublic: boolean = fals if (isPublic && paths) { const ONE_DAY_IN_SECONDS = 60 * 60 * 24; const cacheRules = { - "session": `no-cache`, - "i18n": `no-cache`, + session: `no-cache`, + i18n: `no-cache`, // Revalidation time here should be 1 second, per https://github.com/calcom/cal.com/pull/6823#issuecomment-1423215321 "slots.getSchedule": `no-cache`, // FIXME - "cityTimezones": `max-age=${ONE_DAY_IN_SECONDS}, stale-while-revalidate`, + cityTimezones: `max-age=${ONE_DAY_IN_SECONDS}, stale-while-revalidate`, } as const; const matchedPath = paths.find((v) => v in cacheRules) as keyof typeof cacheRules; @@ -71,4 +73,4 @@ export function createNextApiHandler(router: AnyRouter, isPublic: boolean = fals return defaultHeaders; }, }); -}; +} diff --git a/packages/types/AppHandler.d.ts b/packages/types/AppHandler.d.ts index bf8934092b..9b4eda52d6 100644 --- a/packages/types/AppHandler.d.ts +++ b/packages/types/AppHandler.d.ts @@ -1,7 +1,7 @@ -import { NextApiHandler } from "next"; -import { Session } from "next-auth"; +import type { NextApiHandler } from "next"; +import type { Session } from "next-auth"; -import { Credential } from "@calcom/prisma/client"; +import type { Credential } from "@calcom/prisma/client"; export type AppDeclarativeHandler = { appType: string; diff --git a/yarn.lock b/yarn.lock index 26578d35b3..bbec09add4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -134,6 +134,25 @@ __metadata: languageName: node linkType: hard +"@auth/core@npm:^0.1.4": + version: 0.1.4 + resolution: "@auth/core@npm:0.1.4" + dependencies: + "@panva/hkdf": 1.0.2 + cookie: 0.5.0 + jose: 4.11.1 + oauth4webapi: 2.0.5 + preact: 10.11.3 + preact-render-to-string: 5.2.3 + peerDependencies: + nodemailer: 6.8.0 + peerDependenciesMeta: + nodemailer: + optional: true + checksum: 64854404ea1883e0deb5535b34bed95cd43fc85094aeaf4f15a79e14045020eb944f844defe857edfc8528a0a024be89cbb2a3069dedef0e9217a74ca6c3eb79 + languageName: node + linkType: hard + "@aws-crypto/ie11-detection@npm:^3.0.0": version: 3.0.0 resolution: "@aws-crypto/ie11-detection@npm:3.0.0" @@ -3996,6 +4015,39 @@ __metadata: languageName: unknown linkType: soft +"@calcom/auth@workspace:apps/auth": + version: 0.0.0-use.local + resolution: "@calcom/auth@workspace:apps/auth" + dependencies: + "@auth/core": ^0.1.4 + "@calcom/app-store": "*" + "@calcom/app-store-cli": "*" + "@calcom/config": "*" + "@calcom/core": "*" + "@calcom/dayjs": "*" + "@calcom/embed-core": "workspace:*" + "@calcom/embed-react": "workspace:*" + "@calcom/embed-snippet": "workspace:*" + "@calcom/features": "*" + "@calcom/lib": "*" + "@calcom/prisma": "*" + "@calcom/trpc": "*" + "@calcom/tsconfig": "*" + "@calcom/types": "*" + "@calcom/ui": "*" + "@types/node": 16.9.1 + "@types/react": 18.0.26 + "@types/react-dom": 18.0.9 + eslint: ^8.34.0 + eslint-config-next: ^13.2.1 + next: ^13.2.1 + next-auth: ^4.20.1 + react: ^18.2.0 + react-dom: ^18.2.0 + typescript: ^4.9.4 + languageName: unknown + linkType: soft + "@calcom/caldavcalendar@workspace:packages/app-store/caldavcalendar": version: 0.0.0-use.local resolution: "@calcom/caldavcalendar@workspace:packages/app-store/caldavcalendar" @@ -4990,6 +5042,15 @@ __metadata: languageName: unknown linkType: soft +"@calcom/webex@workspace:packages/app-store/webex": + version: 0.0.0-use.local + resolution: "@calcom/webex@workspace:packages/app-store/webex" + dependencies: + "@calcom/lib": "*" + "@calcom/types": "*" + languageName: unknown + linkType: soft + "@calcom/website@workspace:apps/website": version: 0.0.0-use.local resolution: "@calcom/website@workspace:apps/website" @@ -8336,6 +8397,13 @@ __metadata: languageName: node linkType: hard +"@panva/hkdf@npm:1.0.2": + version: 1.0.2 + resolution: "@panva/hkdf@npm:1.0.2" + checksum: 75183b4d5ea816ef516dcea70985c610683579a9e2ac540c2d59b9a3ed27eedaff830a43a1c43c1683556a457c92ac66e09109ee995ab173090e4042c4c4bb03 + languageName: node + linkType: hard + "@panva/hkdf@npm:^1.0.2": version: 1.0.4 resolution: "@panva/hkdf@npm:1.0.4" @@ -8462,6 +8530,17 @@ __metadata: languageName: node linkType: hard +"@prisma/debug@npm:4.14.0": + version: 4.14.0 + resolution: "@prisma/debug@npm:4.14.0" + dependencies: + "@types/debug": 4.1.7 + debug: 4.3.4 + strip-ansi: 6.0.1 + checksum: 9e285394813ee5304091e82505cb6cb6a456c864f61b3037df4a853725cacfd58728552b8397500ea78a69c4289ce80348d7cece43dfe9c398be93a064a29a32 + languageName: node + linkType: hard + "@prisma/engines-version@npm:4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a": version: 4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a resolution: "@prisma/engines-version@npm:4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a" @@ -8476,7 +8555,19 @@ __metadata: languageName: node linkType: hard -"@prisma/generator-helper@npm:^4.0.0, @prisma/generator-helper@npm:^4.13.0": +"@prisma/generator-helper@npm:^4.0.0": + version: 4.14.0 + resolution: "@prisma/generator-helper@npm:4.14.0" + dependencies: + "@prisma/debug": 4.14.0 + "@types/cross-spawn": 6.0.2 + cross-spawn: 7.0.3 + kleur: 4.1.5 + checksum: 91b358c494e9139875f71217b1af33844f0d1f13e66aac7dde9cd5982c970c4adae6bbacab095b318a2693938e8f5fe07de535937f466ff16d88ab3ec5d29299 + languageName: node + linkType: hard + +"@prisma/generator-helper@npm:^4.13.0": version: 4.13.0 resolution: "@prisma/generator-helper@npm:4.13.0" dependencies: @@ -9951,29 +10042,29 @@ __metadata: languageName: node linkType: hard -"@sentry-internal/tracing@npm:7.50.0": - version: 7.50.0 - resolution: "@sentry-internal/tracing@npm:7.50.0" +"@sentry-internal/tracing@npm:7.51.2": + version: 7.51.2 + resolution: "@sentry-internal/tracing@npm:7.51.2" dependencies: - "@sentry/core": 7.50.0 - "@sentry/types": 7.50.0 - "@sentry/utils": 7.50.0 + "@sentry/core": 7.51.2 + "@sentry/types": 7.51.2 + "@sentry/utils": 7.51.2 tslib: ^1.9.3 - checksum: 5015b91bffa97a78522b1638d409074682b60bfd2a600e0bfb191ce6afd6dd7ee4fcdbf7cdd818142313001b77033b980a8645bfb9939bf123e3513a29d097ad + checksum: a10ee8717b76f140eaa1a94aac67005da98f0aa32a433d33a29265db7197fcb2e4a1e1a21502926f3c1460a5c82a6bc78413013731ac0a3bb41797f25550694e languageName: node linkType: hard -"@sentry/browser@npm:7.50.0": - version: 7.50.0 - resolution: "@sentry/browser@npm:7.50.0" +"@sentry/browser@npm:7.51.2": + version: 7.51.2 + resolution: "@sentry/browser@npm:7.51.2" dependencies: - "@sentry-internal/tracing": 7.50.0 - "@sentry/core": 7.50.0 - "@sentry/replay": 7.50.0 - "@sentry/types": 7.50.0 - "@sentry/utils": 7.50.0 + "@sentry-internal/tracing": 7.51.2 + "@sentry/core": 7.51.2 + "@sentry/replay": 7.51.2 + "@sentry/types": 7.51.2 + "@sentry/utils": 7.51.2 tslib: ^1.9.3 - checksum: fa875610045380cdf8469f4267d149af7e89713137313db2ae671ce9c61eca15bb7a233dd8eeae3f09cb7bfa23bbdfc9e0aca5900ad48d90b25a231f5f87426c + checksum: af027bc6d38dee0a5577ee83b69d57ffce63334ff8232a2ab24ae142aef9c8d50968badcc5bf11ef296d14c92cba7009216b5b0692d9af587ed6a506210bf553 languageName: node linkType: hard @@ -9993,40 +10084,40 @@ __metadata: languageName: node linkType: hard -"@sentry/core@npm:7.50.0": - version: 7.50.0 - resolution: "@sentry/core@npm:7.50.0" +"@sentry/core@npm:7.51.2": + version: 7.51.2 + resolution: "@sentry/core@npm:7.51.2" dependencies: - "@sentry/types": 7.50.0 - "@sentry/utils": 7.50.0 + "@sentry/types": 7.51.2 + "@sentry/utils": 7.51.2 tslib: ^1.9.3 - checksum: d372d6235355e6e63eb82146e1944a323b5b6303f6a4f7df6a39a9713f3a9c4aebb68d3de43178a6028c7b2d6d68a2f7a34c08e33cf45c0596b46ae6fd2bf83e + checksum: f0526ff9cbbea368e50e24ff3ff4b97df8e6d2bede340de69ae2362715bc30d9f1b526eff4eea1f1e891b69e5fec09d263196fa49a1dfeca7aab38e92aa79a90 languageName: node linkType: hard -"@sentry/integrations@npm:7.50.0": - version: 7.50.0 - resolution: "@sentry/integrations@npm:7.50.0" +"@sentry/integrations@npm:7.51.2": + version: 7.51.2 + resolution: "@sentry/integrations@npm:7.51.2" dependencies: - "@sentry/types": 7.50.0 - "@sentry/utils": 7.50.0 + "@sentry/types": 7.51.2 + "@sentry/utils": 7.51.2 localforage: ^1.8.1 tslib: ^1.9.3 - checksum: fad3c7cdf2ae638bb609b57a22af3d9111381b8655e33ef607dd1b13634aecd7644a04d58006ceabd82b7b6c1a9b73bf2403b0b875d0c99229712bbf794be4b7 + checksum: 5e40e34433e5393b7927a3299760924170cfedcd54f2730b71e51363778e3dbd35d139e01c7afbb8cb20a8be1e8548f473515480daf5187b736f3d2de50b2e87 languageName: node linkType: hard "@sentry/nextjs@npm:^7.20.0": - version: 7.50.0 - resolution: "@sentry/nextjs@npm:7.50.0" + version: 7.51.2 + resolution: "@sentry/nextjs@npm:7.51.2" dependencies: "@rollup/plugin-commonjs": 24.0.0 - "@sentry/core": 7.50.0 - "@sentry/integrations": 7.50.0 - "@sentry/node": 7.50.0 - "@sentry/react": 7.50.0 - "@sentry/types": 7.50.0 - "@sentry/utils": 7.50.0 + "@sentry/core": 7.51.2 + "@sentry/integrations": 7.51.2 + "@sentry/node": 7.51.2 + "@sentry/react": 7.51.2 + "@sentry/types": 7.51.2 + "@sentry/utils": 7.51.2 "@sentry/webpack-plugin": 1.20.0 chalk: 3.0.0 rollup: 2.78.0 @@ -10039,66 +10130,66 @@ __metadata: peerDependenciesMeta: webpack: optional: true - checksum: 33975c45ee4f2a48ea6d7639435e2a3810fdb0983b1c265f4989b0e1af5ef8405512330ee662f092f2d47719672cbafe4f8cab19b474e035cda552a61a80ad3e + checksum: b57b03f31e627de19d2fdf8444cdcbff1dfa38a556ef33ab1a4917ca0ab03e92037ecea74437b69b372f2d298b751b29a1870b05517e9947ec70d1fb172db51b languageName: node linkType: hard -"@sentry/node@npm:7.50.0": - version: 7.50.0 - resolution: "@sentry/node@npm:7.50.0" +"@sentry/node@npm:7.51.2": + version: 7.51.2 + resolution: "@sentry/node@npm:7.51.2" dependencies: - "@sentry-internal/tracing": 7.50.0 - "@sentry/core": 7.50.0 - "@sentry/types": 7.50.0 - "@sentry/utils": 7.50.0 + "@sentry-internal/tracing": 7.51.2 + "@sentry/core": 7.51.2 + "@sentry/types": 7.51.2 + "@sentry/utils": 7.51.2 cookie: ^0.4.1 https-proxy-agent: ^5.0.0 lru_map: ^0.3.3 tslib: ^1.9.3 - checksum: b65d69a2dd3242bf0bfe92059bfd2a39a4d46cd988544ee228a8e2df39b12d1942e8d343fe69bc9f945d9b2971f087e69ffb9a1774ee38a6cf1d3110d09efc4e + checksum: 86abe40ccd69268c0ab3cf57ef9f93126e70a222da962476b3d8157c2f8225537344bfe29fc0bbb2148f9f30d3377afd6f1b20cbe3fa8f349bc27796171ad18b languageName: node linkType: hard -"@sentry/react@npm:7.50.0": - version: 7.50.0 - resolution: "@sentry/react@npm:7.50.0" +"@sentry/react@npm:7.51.2": + version: 7.51.2 + resolution: "@sentry/react@npm:7.51.2" dependencies: - "@sentry/browser": 7.50.0 - "@sentry/types": 7.50.0 - "@sentry/utils": 7.50.0 + "@sentry/browser": 7.51.2 + "@sentry/types": 7.51.2 + "@sentry/utils": 7.51.2 hoist-non-react-statics: ^3.3.2 tslib: ^1.9.3 peerDependencies: react: 15.x || 16.x || 17.x || 18.x - checksum: 28044dc61b726bd08f8a68a9b0a04aa3bda52a3d8530005a6eb005c64099612a97b9d1d4028a8f1f2d3e004befff759a8c0ccd1ce0c845761143b50ed7188934 + checksum: af462926edeefcc4ce25a33621e01353691bfbe63ff51d88fff5000f826336e903c49c6d716a02c88b4db30bcb09b9f089c55cdf52af1dc6a96562a97f18ff8c languageName: node linkType: hard -"@sentry/replay@npm:7.50.0": - version: 7.50.0 - resolution: "@sentry/replay@npm:7.50.0" +"@sentry/replay@npm:7.51.2": + version: 7.51.2 + resolution: "@sentry/replay@npm:7.51.2" dependencies: - "@sentry/core": 7.50.0 - "@sentry/types": 7.50.0 - "@sentry/utils": 7.50.0 - checksum: 3ad1d6af5a74a8896c7bad99a7bc38ebd8cd60824e4d9a1ddc2b240a5bf9215aef0c387a43169e625fba00ae39a8cb27efe27d6ddb6c5c6002d1a2b445740374 + "@sentry/core": 7.51.2 + "@sentry/types": 7.51.2 + "@sentry/utils": 7.51.2 + checksum: 2f6c5b9a31576049e9385aebb384237372889ecf07a6bc9a72a50d4b21584fed2c77bb6897ec7c6fd790469fbcc3c62affeb617475cf39b0993b01a400611aed languageName: node linkType: hard -"@sentry/types@npm:7.50.0": - version: 7.50.0 - resolution: "@sentry/types@npm:7.50.0" - checksum: 9f058231bf22ca9b1e83cf43607be5d7f174a75ac0477b3c565fec7e9431897b10aadfac94cf7eb5d84888e3520383ea2f4176cf2ad3928a8da78fc8b2e2481d +"@sentry/types@npm:7.51.2": + version: 7.51.2 + resolution: "@sentry/types@npm:7.51.2" + checksum: 6d200e1b4f15f0d4911a1579c44e5fabe73220b2b0ec297ece88503e93249fd1f268f5b6319b22f58334638a9f7e611a5cf89813d087b74b2cc4e7d6b0c027ca languageName: node linkType: hard -"@sentry/utils@npm:7.50.0": - version: 7.50.0 - resolution: "@sentry/utils@npm:7.50.0" +"@sentry/utils@npm:7.51.2": + version: 7.51.2 + resolution: "@sentry/utils@npm:7.51.2" dependencies: - "@sentry/types": 7.50.0 + "@sentry/types": 7.51.2 tslib: ^1.9.3 - checksum: 0fdb94a46d19d9b98b9be2cd2e7f8c4c54b7c9d92091cec2f1093388398d8a400be346a71108edf2b3351ee57edb6f77a0bff7026a9f1b9d91699a6a8c08f49b + checksum: 626e4ceb17677f67b5276624509e14bc3849244d4ea103df25ab5a3b2f341d2a3e791d9bb366dc79aa39b4085418a7562ee16037ceb8d05eb4f1b4874a8f3d06 languageName: node linkType: hard @@ -26535,6 +26626,13 @@ __metadata: languageName: node linkType: hard +"jose@npm:4.11.1": + version: 4.11.1 + resolution: "jose@npm:4.11.1" + checksum: cd15cba258d0fd20f6168631ce2e94fda8442df80e43c1033c523915cecdf390a1cc8efe0eab0c2d65935ca973d791c668aea80724d2aa9c2879d4e70f3081d7 + languageName: node + linkType: hard + "jose@npm:4.12.0": version: 4.12.0 resolution: "jose@npm:4.12.0" @@ -27165,6 +27263,13 @@ __metadata: languageName: node linkType: hard +"kleur@npm:4.1.5": + version: 4.1.5 + resolution: "kleur@npm:4.1.5" + checksum: 1dc476e32741acf0b1b5b0627ffd0d722e342c1b0da14de3e8ae97821327ca08f9fb944542fb3c126d90ac5f27f9d804edbe7c585bf7d12ef495d115e0f22c12 + languageName: node + linkType: hard + "kleur@npm:^3.0.3": version: 3.0.3 resolution: "kleur@npm:3.0.3" @@ -30637,6 +30742,13 @@ __metadata: languageName: node linkType: hard +"oauth4webapi@npm:2.0.5": + version: 2.0.5 + resolution: "oauth4webapi@npm:2.0.5" + checksum: 32d0cb7b1cca42d51dfb88075ca2d69fe33172a807e8ea50e317d17cab3bc80588ab8ebcb7eb4600c371a70af4674595b4b341daf6f3a655f1efa1ab715bb6c9 + languageName: node + linkType: hard + "oauth@npm:^0.9.15": version: 0.9.15 resolution: "oauth@npm:0.9.15" @@ -32327,6 +32439,17 @@ __metadata: languageName: node linkType: hard +"preact-render-to-string@npm:5.2.3": + version: 5.2.3 + resolution: "preact-render-to-string@npm:5.2.3" + dependencies: + pretty-format: ^3.8.0 + peerDependencies: + preact: ">=10" + checksum: 6e46288d8956adde35b9fe3a21aecd9dea29751b40f0f155dea62f3896f27cb8614d457b32f48d33909d2da81135afcca6c55077520feacd7d15164d1371fb44 + languageName: node + linkType: hard + "preact-render-to-string@npm:^5.1.19": version: 5.2.6 resolution: "preact-render-to-string@npm:5.2.6" @@ -32338,6 +32461,13 @@ __metadata: languageName: node linkType: hard +"preact@npm:10.11.3, preact@npm:^10.6.3": + version: 10.11.3 + resolution: "preact@npm:10.11.3" + checksum: 9387115aa0581e8226309e6456e9856f17dfc0e3d3e63f774de80f3d462a882ba7c60914c05942cb51d51e23e120dcfe904b8d392d46f29ad15802941fe7a367 + languageName: node + linkType: hard + "preact@npm:10.4.1": version: 10.4.1 resolution: "preact@npm:10.4.1" @@ -32352,13 +32482,6 @@ __metadata: languageName: node linkType: hard -"preact@npm:^10.6.3": - version: 10.11.3 - resolution: "preact@npm:10.11.3" - checksum: 9387115aa0581e8226309e6456e9856f17dfc0e3d3e63f774de80f3d462a882ba7c60914c05942cb51d51e23e120dcfe904b8d392d46f29ad15802941fe7a367 - languageName: node - linkType: hard - "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1"