Webex integration (#7651)
* start webex app creation * webex integration wip * fix lint errors * fix lint errors * add webex env vars in appStore.example * webex app wip * fix lint errors * edit webex oauth scopes * add location in webex app config * add site url placeholder and regex in webex config location * debug translateEvent * fix utc formatting for event start time, add test boilerplate for webex, add envs * fix location and datetime formatting * get correct videoCredentials for deleteMeeting * Move webex specific readme content to webex README * Fix app not visible in app-store * Delete setup route * add webex icon * delete prev icon * webex api fix * add app screenshots * Revert tests changes as they dont run * Use config instead of hardcoding vales * Update README * Remove all env variables related to WEBEX app. They can be added through settings->admin->apps interface * update from origin * fix icon path * update webex readme * Update yarn.lock * update webex readme * Remove unnecessary URL from webex * revert changes in cancel booking handler * simply webex zod schemas, logs for debugging --------- Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>pull/8836/head
parent
067b77faf1
commit
e2cfd0d1d1
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
items:
|
||||
- 1.jpeg
|
||||
- 2.jpeg
|
||||
- 3.jpeg
|
||||
- 4.jpeg
|
||||
---
|
||||
|
||||
{DESCRIPTION}
|
|
@ -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 `<Cal.com URL>/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
|
|
@ -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) }),
|
||||
});
|
|
@ -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 }));
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export { default as add } from "./add";
|
||||
export { default as callback } from "./callback";
|
|
@ -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"
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * as api from "./api";
|
||||
export * as lib from "./lib";
|
|
@ -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<typeof webexEventResultSchema>;
|
||||
|
||||
/** @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<typeof webexTokenSchema>;
|
||||
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<VideoCallData> => {
|
||||
/** @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<void> => {
|
||||
/** @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<VideoCallData> => {
|
||||
/** @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;
|
|
@ -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);
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
export { getWebexAppKeys } from "./getWebexAppKeys";
|
||||
export { default as VideoApiAdapter } from "./VideoApiAdapter";
|
|
@ -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"
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 332 KiB |
Binary file not shown.
After Width: | Height: | Size: 391 KiB |
Binary file not shown.
After Width: | Height: | Size: 259 KiB |
Binary file not shown.
After Width: | Height: | Size: 247 KiB |
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
|
@ -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),
|
||||
});
|
|
@ -20,7 +20,7 @@ const zoomEventResultSchema = z.object({
|
|||
|
||||
export type ZoomEventResult = z.infer<typeof zoomEventResultSchema>;
|
||||
|
||||
// @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(),
|
||||
|
|
|
@ -142,6 +142,7 @@ const updateMeeting = async (
|
|||
const deleteMeeting = async (credential: CredentialPayload, uid: string): Promise<unknown> => {
|
||||
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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
|
@ -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"],
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
275
yarn.lock
275
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"
|
||||
|
|
Loading…
Reference in New Issue