Compare commits

...

2 Commits

Author SHA1 Message Date
Leo Giovanetti 13bbbbb78a typo 2023-07-09 21:03:11 -03:00
Leo Giovanetti c4f3badcee Oura Ring app 2023-07-09 20:59:30 -03:00
23 changed files with 536 additions and 8 deletions

View File

@ -5,6 +5,7 @@
# - GOOGLE CALENDAR/MEET/LOGIN
# - HUBSPOT
# - OFFICE 365
# - OURA RING
# - SLACK
# - STRIPE
# - TANDEM
@ -53,6 +54,12 @@ HUBSPOT_CLIENT_SECRET=""
MS_GRAPH_CLIENT_ID=
MS_GRAPH_CLIENT_SECRET=
# - OURA RING
# Used for the Oura Ring integration
# @see https://github.com/calcom/cal.com/#Obtaining-Oura-Ring-Client-ID-and-Secret
OURA_RING_CLIENT_ID=
OURA_RING_CLIENT_SECRET=
# - SLACK
# @see https://github.com/calcom/cal.com/#obtaining-slack-client-id-and-secret-and-signing-secret
SLACK_SIGNING_SECRET=

View File

@ -475,6 +475,14 @@ 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 Oura Ring Client ID and Secret
1. Go to [Oura Ring Developer portal](https://cloud.ouraring.com/user/sign-in?next=%2Foauth%2Fapplications)
2. Click button "New application"
3. Fill in the information for your new Cal.com Oura Ring application
7. Set the Redirect URL to `<Cal.com URL>/api/integrations/ouraring/callback` replacing Cal.com URL with the URI at which your application runs.
9. Click the "Save" button at the bottom.
6. Now copy the Client ID and Client Secret to your `.env` file into the `OURA_RING_CLIENT_ID` and `OURA_RING_CLIENT_SECRET` fields.
### Obtaining Webex Client ID and Secret
[See Webex Readme](./packages/app-store/webex/)

View File

@ -31,6 +31,7 @@ import mirotalk_config_json from "./mirotalk/config.json";
import n8n_config_json from "./n8n/config.json";
import { metadata as office365calendar__metadata_ts } from "./office365calendar/_metadata";
import office365video_config_json from "./office365video/config.json";
import ouraring_config_json from "./ouraring/config.json";
import ping_config_json from "./ping/config.json";
import pipedream_config_json from "./pipedream/config.json";
import plausible_config_json from "./plausible/config.json";
@ -96,6 +97,7 @@ export const appStoreMetadata = {
n8n: n8n_config_json,
office365calendar: office365calendar__metadata_ts,
office365video: office365video_config_json,
ouraring: ouraring_config_json,
ping: ping_config_json,
pipedream: pipedream_config_json,
plausible: plausible_config_json,

View File

@ -31,6 +31,7 @@ export const apiHandlers = {
n8n: import("./n8n/api"),
office365calendar: import("./office365calendar/api"),
office365video: import("./office365video/api"),
ouraring: import("./ouraring/api"),
ping: import("./ping/api"),
pipedream: import("./pipedream/api"),
plausible: import("./plausible/api"),

View File

@ -13,6 +13,7 @@ const appStore = {
larkcalendar: () => import("./larkcalendar"),
office365calendar: () => import("./office365calendar"),
office365video: () => import("./office365video"),
ouraring: () => import("./ouraring"),
plausible: () => import("./plausible"),
salesforce: () => import("./salesforce"),
zohocrm: () => import("./zohocrm"),

View File

@ -0,0 +1,6 @@
---
items:
- 1.jpeg
---
{DESCRIPTION}

View File

@ -0,0 +1,31 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { stringify } from "querystring";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { encodeOAuthState } from "../../_utils/encodeOAuthState";
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
const scopes = ["daily"];
let client_id = "";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "GET") {
return res.status(400).send("Method not allowed");
}
const appKeys = await getAppKeysFromSlug("ouraring");
if (typeof appKeys.client_id === "string") client_id = appKeys.client_id;
if (!client_id) return res.status(400).json({ message: "Oura Ring client id missing." });
const state = encodeOAuthState(req);
const params = {
response_type: "code",
scope: scopes.join(" "),
client_id,
redirect_uri: WEBAPP_URL + "/api/integrations/ouraring/callback",
state,
};
const query = stringify(params);
const url = `https://cloud.ouraring.com/oauth/authorize?${query}`;
res.status(200).json({ url });
}

View File

@ -0,0 +1,72 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
import prisma from "@calcom/prisma";
import { decodeOAuthState } from "../../_utils/decodeOAuthState";
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
const scopes = ["daily"];
let client_id = "";
let client_secret = "";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { code } = req.query;
if (typeof code !== "string") {
res.status(400).json({ message: "No code returned" });
return;
}
const appKeys = await getAppKeysFromSlug("ouraring");
if (typeof appKeys.client_id === "string") client_id = appKeys.client_id;
if (typeof appKeys.client_secret === "string") client_secret = appKeys.client_secret;
if (!client_id) return res.status(400).json({ message: "Oura Ring client id missing." });
if (!client_secret) return res.status(400).json({ message: "Oura Ring client secret missing." });
const toUrlEncoded = (payload: Record<string, string>) =>
Object.keys(payload)
.map((key) => key + "=" + encodeURIComponent(payload[key]))
.join("&");
const body = toUrlEncoded({
client_id,
grant_type: "authorization_code",
code,
scope: scopes.join(" "),
redirect_uri: WEBAPP_URL + "/api/integrations/ouraring/callback",
client_secret,
});
const response = await fetch("https://cloud.ouraring.com/oauth/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
},
body,
});
const responseBody = await response.json();
if (!response.ok) {
return res.redirect("/apps/installed?error=" + JSON.stringify(responseBody));
}
responseBody.expires_in = Math.round(+new Date() / 1000 + responseBody.expires_in);
await prisma.credential.create({
data: {
type: "oura_ring_other_calendar",
key: responseBody,
userId: req.session?.user.id,
appId: "ouraring",
},
});
const state = decodeOAuthState(req);
return res.redirect(
getSafeRedirectUrl(state?.returnTo.replace("http:/", "http://")) ??
getInstalledAppPath({ variant: "other", slug: "ouraring" })
);
}

View File

@ -0,0 +1,2 @@
export { default as add } from "./add";
export { default as callback } from "./callback";

View File

@ -0,0 +1,16 @@
{
"name": "Oura Ring",
"slug": "ouraring",
"type": "oura_ring_other_calendar",
"logo": "icon.svg",
"url": "https://ouraring.com",
"variant": "other",
"categories": ["other"],
"publisher": "Leo Giovanetti",
"email": "hello@leog.me",
"description": "Rely on your Oura Ring Readiness score to allow bookings on the days you have data loaded from your ring. A score equal to or greater than 70, means you've recovered well enough so bookings are not blocked, below that, you need to pay attention as you're not fully recovered, so bookings will be blocked. No data means bookings are not blocked.",
"isTemplate": false,
"__createdUsingCli": true,
"__template": "general-app-settings",
"dirName": "ouraring"
}

View File

@ -0,0 +1,2 @@
export * as api from "./api";
export * as lib from "./lib";

View File

@ -0,0 +1,180 @@
import { z } from "zod";
import dayjs from "@calcom/dayjs";
import { handleErrorsJson } from "@calcom/lib/errors";
import logger from "@calcom/lib/logger";
import prisma from "@calcom/prisma";
import type {
Calendar,
EventBusyDate,
IntegrationCalendar,
NewCalendarEventType,
} from "@calcom/types/Calendar";
import type { CredentialPayload } from "@calcom/types/Credential";
import getParsedAppKeysFromSlug from "../../_utils/getParsedAppKeysFromSlug";
export type OuraRingAuthCredentials = {
access_token: string;
refresh_token: string;
expires_in: number;
};
type OuraRingReturnedData = {
day: string;
score: number;
};
const toUrlEncoded = (payload: Record<string, string>) =>
Object.keys(payload)
.map((key) => key + "=" + encodeURIComponent(payload[key]))
.join("&");
const ouraRingAppKeysSchema = z.object({
client_id: z.string(),
client_secret: z.string(),
});
export const getOuraRingAppKeys = async () => {
return getParsedAppKeysFromSlug("ouraring", ouraRingAppKeysSchema);
};
const refreshTokenResponseSchema = z.object({
access_token: z.string(),
expires_in: z
.number()
.transform((currentTimeOffsetInSeconds) => Math.round(+new Date() / 1000 + currentTimeOffsetInSeconds)),
refresh_token: z.string().optional(),
});
export default class OuraRingCalendarService implements Calendar {
private integrationName = "";
private log: typeof logger;
private accessToken: string | null = null;
auth: { getToken: () => Promise<string> };
private apiUrl = "https://api.ouraring.com/v2";
constructor(credential: CredentialPayload) {
this.integrationName = "oura_ring_other_calendar";
this.auth = this.ouraRingAuth(credential);
this.log = logger.getChildLogger({ prefix: [`[[lib] ${this.integrationName}`] });
}
createEvent(): Promise<NewCalendarEventType | undefined> {
return Promise.resolve(undefined);
}
updateEvent(): Promise<NewCalendarEventType | NewCalendarEventType[]> {
return Promise.resolve([]);
}
deleteEvent(): Promise<unknown> {
return Promise.resolve();
}
async getAvailability(dateFrom: string, dateTo: string): Promise<EventBusyDate[]> {
try {
const response = await this.fetcher(
`/usercollection/daily_readiness?${toUrlEncoded({
start_date: dayjs(dateFrom).format("YYYY-MM-DD"),
end_date: dayjs(dateTo).format("YYYY-MM-DD"),
})}`
);
if (!response.ok) {
return Promise.reject([]);
}
const ouraInfo = (await response.json()) as { data: OuraRingReturnedData[] };
if (ouraInfo.data.length > 0) {
const result = ouraInfo.data.flatMap((data: OuraRingReturnedData) => {
if (data.score < 70) {
return [
{
start: dayjs(data.day).startOf("day").toISOString(),
end: dayjs(data.day).endOf("day").toISOString(),
source: "Oura Ring",
},
];
}
return [];
});
return Promise.resolve(result);
} else {
this.log.debug("No data returned");
}
return Promise.resolve([]);
} catch (err) {
this.log.error(err);
return Promise.reject([]);
}
}
async listCalendars(): Promise<IntegrationCalendar[]> {
return Promise.resolve([]);
}
private ouraRingAuth = (credential: CredentialPayload) => {
const isExpired = (expiryDate: number) => {
if (!expiryDate) {
return true;
} else {
return expiryDate < Math.round(+new Date() / 1000);
}
};
const ouraRingCredentials = credential.key as OuraRingAuthCredentials;
const refreshAccessToken = async (ouraRingCredentials: OuraRingAuthCredentials) => {
const { client_id, client_secret } = await getOuraRingAppKeys();
const response = await fetch("https://cloud.ouraring.com/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: toUrlEncoded({
client_id,
refresh_token: ouraRingCredentials.refresh_token,
grant_type: "refresh_token",
client_secret,
}),
});
const responseJson = await handleErrorsJson(response);
const tokenResponse = refreshTokenResponseSchema.safeParse(responseJson);
ouraRingCredentials = { ...ouraRingCredentials, ...(tokenResponse.success && tokenResponse.data) };
if (!tokenResponse.success) {
this.log.error(
"Oura Ring error grabbing new tokens ~ zodError:",
tokenResponse.error,
"API response:",
responseJson
);
}
await prisma.credential.update({
where: {
id: credential.id,
},
data: {
key: ouraRingCredentials,
},
});
return ouraRingCredentials.access_token;
};
return {
getToken: () =>
isExpired(ouraRingCredentials.expires_in)
? refreshAccessToken(ouraRingCredentials)
: Promise.resolve(ouraRingCredentials.access_token),
};
};
private fetcher = async (endpoint: string, init?: RequestInit | undefined) => {
this.accessToken = await this.auth.getToken();
return fetch(`${this.apiUrl}${endpoint}`, {
method: "GET",
headers: {
Authorization: "Bearer " + this.accessToken,
"Content-Type": "application/json",
},
...init,
});
};
}

View File

@ -0,0 +1 @@
export { default as CalendarService } from "./CalendarService";

View File

@ -0,0 +1,14 @@
{
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"name": "@calcom/ouraring",
"version": "0.0.0",
"main": "./index.ts",
"dependencies": {
"@calcom/lib": "*"
},
"devDependencies": {
"@calcom/types": "*"
},
"description": "Rely on your Oura Ring Readiness score to allow bookings on the days you have data loaded from your ring. A score equal to or greater than 70, means you've recovered well enough so bookings are not blocked, below that, you need to pay attention as you're not fully recovered, so bookings will be blocked. No data means bookings are not blocked."
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 KiB

View File

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="800px" height="800px" viewBox="0 0 48 48">
<defs>
<style>.a{fill:none;stroke:#929292;stroke-linecap:round;stroke-linejoin:round;stroke-width: 3;}</style>
</defs>
<circle class="a" cx="24" cy="27" r="16.5" />
<line class="a" x1="16.2171" y1="4.5" x2="31.7829" y2="4.5" />
</svg>

After

Width:  |  Height:  |  Size: 337 B

View File

@ -150,7 +150,7 @@ export const getCachedResults = async (
* TODO: Migrate credential type or appId
*/
const passedSelectedCalendars = selectedCalendars.filter((sc) => sc.integration === type);
if (!passedSelectedCalendars.length) return [];
if (!passedSelectedCalendars.length && type.indexOf("other_calendar") === -1) return [];
/** We extract external Ids so we don't cache too much */
const selectedCalendarIds = passedSelectedCalendars.map((sc) => sc.externalId);
/** If we don't then we actually fetch external calendars (which can be very slow) */

View File

@ -23,7 +23,7 @@ const getCalendarsEvents = async (
* TODO: Migrate credential type or appId
*/
const passedSelectedCalendars = selectedCalendars.filter((sc) => sc.integration === type);
if (!passedSelectedCalendars.length) return [];
if (!passedSelectedCalendars.length && type.indexOf("other_calendar") === -1) return [];
/** We extract external Ids so we don't cache too much */
const selectedCalendarIds = passedSelectedCalendars.map((sc) => sc.externalId);
/** If we don't then we actually fetch external calendars (which can be very slow) */

View File

@ -7,7 +7,10 @@ export const getSafeRedirectUrl = (url = "") => {
}
//It is important that this fn is given absolute URL because urls that don't start with HTTP can still deceive browser into redirecting to another domain
if (url.search(/^https?:\/\//) === -1) {
if (
(process.env.NODE_ENV === "production" && url.search(/^https?:/) === -1) ||
(process.env.NODE_ENV !== "production" && url.search(/^http?:/) === -1)
) {
throw new Error("Pass an absolute URL");
}

View File

@ -279,6 +279,12 @@ export default async function main() {
base_url: (process.env.TANDEM_BASE_URL as string) || "https://tandem.chat",
});
}
if (process.env.OURA_RING_CLIENT_ID && process.env.OURA_RING_CLIENT_SECRET) {
await createApp("ouraring", "ouraring", ["other"], "oura_ring_other_calendar", {
client_id: process.env.OURA_RING_CLIENT_ID as string,
client_secret: process.env.OURA_RING_CLIENT_SECRET as string,
});
}
if (process.env.ZOOM_CLIENT_ID && process.env.ZOOM_CLIENT_SECRET) {
await createApp("zoom", "zoomvideo", ["conferencing"], "zoom_video", {
client_id: process.env.ZOOM_CLIENT_ID,

View File

@ -215,7 +215,7 @@ export interface IntegrationCalendar extends Ensure<Partial<SelectedCalendar>, "
}
export interface Calendar {
createEvent(event: CalendarEvent): Promise<NewCalendarEventType>;
createEvent(event: CalendarEvent): Promise<NewCalendarEventType | undefined>;
updateEvent(
uid: string,

View File

@ -239,6 +239,8 @@
"NEXTAUTH_URL",
"NODE_ENV",
"ORGANIZATIONS_ENABLED",
"OURA_RING_CLIENT_ID",
"OURA_RING_CLIENT_SECRET",
"PAYMENT_FEE_FIXED",
"PAYMENT_FEE_PERCENTAGE",
"PLAYWRIGHT_HEADLESS",

175
yarn.lock
View File

@ -3839,15 +3839,13 @@ __metadata:
"@calcom/ui": "*"
"@types/node": 16.9.1
"@types/react": 18.0.26
"@types/react-dom": ^18.0.9
"@types/react-dom": 18.0.9
eslint: ^8.34.0
eslint-config-next: ^13.2.1
next: ^13.4.6
next: ^13.2.1
next-auth: ^4.20.1
postcss: ^8.4.18
react: ^18.2.0
react-dom: ^18.2.0
tailwindcss: ^3.3.1
typescript: ^4.9.4
languageName: unknown
linkType: soft
@ -4354,6 +4352,15 @@ __metadata:
languageName: unknown
linkType: soft
"@calcom/ouraring@workspace:packages/app-store/ouraring":
version: 0.0.0-use.local
resolution: "@calcom/ouraring@workspace:packages/app-store/ouraring"
dependencies:
"@calcom/lib": "*"
"@calcom/types": "*"
languageName: unknown
linkType: soft
"@calcom/ping@workspace:packages/app-store/ping":
version: 0.0.0-use.local
resolution: "@calcom/ping@workspace:packages/app-store/ping"
@ -4884,6 +4891,7 @@ __metadata:
"@types/node": 16.9.1
"@types/react": 18.0.26
"@types/react-gtm-module": ^2.0.1
"@types/xml2js": ^0.4.11
"@vercel/analytics": ^0.1.6
"@vercel/edge-functions-ui": ^0.2.1
"@vercel/og": ^0.5.0
@ -4927,12 +4935,14 @@ __metadata:
react-merge-refs: 1.1.0
react-twemoji: ^0.3.0
react-use-measure: ^2.1.1
react-wrap-balancer: ^1.0.0
remark: ^14.0.2
remark-html: ^14.0.1
stripe: ^9.16.0
tailwindcss: ^3.3.1
typescript: ^4.9.4
wait-on: ^7.0.1
xml2js: ^0.6.0
zod: ^3.20.2
languageName: unknown
linkType: soft
@ -7382,6 +7392,13 @@ __metadata:
languageName: node
linkType: hard
"@next/env@npm:13.4.9":
version: 13.4.9
resolution: "@next/env@npm:13.4.9"
checksum: 625f01571ac5dabbb90567a987fdc16eca86cd9cfd592edabf7d3e3024972cd83564d53202293163851b85fa643f846a5757c8696a3c44315c98dae5b634f122
languageName: node
linkType: hard
"@next/eslint-plugin-next@npm:13.2.1":
version: 13.2.1
resolution: "@next/eslint-plugin-next@npm:13.2.1"
@ -7398,6 +7415,13 @@ __metadata:
languageName: node
linkType: hard
"@next/swc-darwin-arm64@npm:13.4.9":
version: 13.4.9
resolution: "@next/swc-darwin-arm64@npm:13.4.9"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@next/swc-darwin-x64@npm:13.4.6":
version: 13.4.6
resolution: "@next/swc-darwin-x64@npm:13.4.6"
@ -7405,6 +7429,13 @@ __metadata:
languageName: node
linkType: hard
"@next/swc-darwin-x64@npm:13.4.9":
version: 13.4.9
resolution: "@next/swc-darwin-x64@npm:13.4.9"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@next/swc-linux-arm64-gnu@npm:13.4.6":
version: 13.4.6
resolution: "@next/swc-linux-arm64-gnu@npm:13.4.6"
@ -7412,6 +7443,13 @@ __metadata:
languageName: node
linkType: hard
"@next/swc-linux-arm64-gnu@npm:13.4.9":
version: 13.4.9
resolution: "@next/swc-linux-arm64-gnu@npm:13.4.9"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@next/swc-linux-arm64-musl@npm:13.4.6":
version: 13.4.6
resolution: "@next/swc-linux-arm64-musl@npm:13.4.6"
@ -7419,6 +7457,13 @@ __metadata:
languageName: node
linkType: hard
"@next/swc-linux-arm64-musl@npm:13.4.9":
version: 13.4.9
resolution: "@next/swc-linux-arm64-musl@npm:13.4.9"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@next/swc-linux-x64-gnu@npm:13.4.6":
version: 13.4.6
resolution: "@next/swc-linux-x64-gnu@npm:13.4.6"
@ -7426,6 +7471,13 @@ __metadata:
languageName: node
linkType: hard
"@next/swc-linux-x64-gnu@npm:13.4.9":
version: 13.4.9
resolution: "@next/swc-linux-x64-gnu@npm:13.4.9"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@next/swc-linux-x64-musl@npm:13.4.6":
version: 13.4.6
resolution: "@next/swc-linux-x64-musl@npm:13.4.6"
@ -7433,6 +7485,13 @@ __metadata:
languageName: node
linkType: hard
"@next/swc-linux-x64-musl@npm:13.4.9":
version: 13.4.9
resolution: "@next/swc-linux-x64-musl@npm:13.4.9"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@next/swc-win32-arm64-msvc@npm:13.4.6":
version: 13.4.6
resolution: "@next/swc-win32-arm64-msvc@npm:13.4.6"
@ -7440,6 +7499,13 @@ __metadata:
languageName: node
linkType: hard
"@next/swc-win32-arm64-msvc@npm:13.4.9":
version: 13.4.9
resolution: "@next/swc-win32-arm64-msvc@npm:13.4.9"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@next/swc-win32-ia32-msvc@npm:13.4.6":
version: 13.4.6
resolution: "@next/swc-win32-ia32-msvc@npm:13.4.6"
@ -7447,6 +7513,13 @@ __metadata:
languageName: node
linkType: hard
"@next/swc-win32-ia32-msvc@npm:13.4.9":
version: 13.4.9
resolution: "@next/swc-win32-ia32-msvc@npm:13.4.9"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
"@next/swc-win32-x64-msvc@npm:13.4.6":
version: 13.4.6
resolution: "@next/swc-win32-x64-msvc@npm:13.4.6"
@ -7454,6 +7527,13 @@ __metadata:
languageName: node
linkType: hard
"@next/swc-win32-x64-msvc@npm:13.4.9":
version: 13.4.9
resolution: "@next/swc-win32-x64-msvc@npm:13.4.9"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@node-ipc/js-queue@npm:2.0.3":
version: 2.0.3
resolution: "@node-ipc/js-queue@npm:2.0.3"
@ -12790,6 +12870,15 @@ __metadata:
languageName: node
linkType: hard
"@types/xml2js@npm:^0.4.11":
version: 0.4.11
resolution: "@types/xml2js@npm:0.4.11"
dependencies:
"@types/node": "*"
checksum: d0e7d2a8c7e0a53147c777a6756bdea0eb26a3de0d3c1f42c17e63332e4e2dc532f687c19ca26a0518336f5de60e1ec4345d7ef71e7ba0656fcf3390c9388eb5
languageName: node
linkType: hard
"@types/yargs-parser@npm:*":
version: 21.0.0
resolution: "@types/yargs-parser@npm:21.0.0"
@ -26998,6 +27087,65 @@ __metadata:
languageName: node
linkType: hard
"next@npm:^13.2.1":
version: 13.4.9
resolution: "next@npm:13.4.9"
dependencies:
"@next/env": 13.4.9
"@next/swc-darwin-arm64": 13.4.9
"@next/swc-darwin-x64": 13.4.9
"@next/swc-linux-arm64-gnu": 13.4.9
"@next/swc-linux-arm64-musl": 13.4.9
"@next/swc-linux-x64-gnu": 13.4.9
"@next/swc-linux-x64-musl": 13.4.9
"@next/swc-win32-arm64-msvc": 13.4.9
"@next/swc-win32-ia32-msvc": 13.4.9
"@next/swc-win32-x64-msvc": 13.4.9
"@swc/helpers": 0.5.1
busboy: 1.6.0
caniuse-lite: ^1.0.30001406
postcss: 8.4.14
styled-jsx: 5.1.1
watchpack: 2.4.0
zod: 3.21.4
peerDependencies:
"@opentelemetry/api": ^1.1.0
fibers: ">= 3.1.0"
react: ^18.2.0
react-dom: ^18.2.0
sass: ^1.3.0
dependenciesMeta:
"@next/swc-darwin-arm64":
optional: true
"@next/swc-darwin-x64":
optional: true
"@next/swc-linux-arm64-gnu":
optional: true
"@next/swc-linux-arm64-musl":
optional: true
"@next/swc-linux-x64-gnu":
optional: true
"@next/swc-linux-x64-musl":
optional: true
"@next/swc-win32-arm64-msvc":
optional: true
"@next/swc-win32-ia32-msvc":
optional: true
"@next/swc-win32-x64-msvc":
optional: true
peerDependenciesMeta:
"@opentelemetry/api":
optional: true
fibers:
optional: true
sass:
optional: true
bin:
next: dist/bin/next
checksum: 7adb9919dc50598e53bf32da2d0f90da325b66a23cb16c291c276d0683f81bade0bb21aeaeede10154ef53eabcb4953bf883f4d752852a62a6bd7be42021aef9
languageName: node
linkType: hard
"next@npm:^13.4.6":
version: 13.4.6
resolution: "next@npm:13.4.6"
@ -30755,6 +30903,15 @@ __metadata:
languageName: node
linkType: hard
"react-wrap-balancer@npm:^1.0.0":
version: 1.0.0
resolution: "react-wrap-balancer@npm:1.0.0"
peerDependencies:
react: ">=16.8.0 || ^17.0.0 || ^18"
checksum: a3e263528b940e555894914d3a0d474cf60594578b4f2a34646d36ea5d9603f75270ec30ff1a7df3a9637cf014d6414c75a70abb82b0c4223bfbeda557ac19ca
languageName: node
linkType: hard
"react@npm:^18.2.0":
version: 18.2.0
resolution: "react@npm:18.2.0"
@ -37328,6 +37485,16 @@ __metadata:
languageName: node
linkType: hard
"xml2js@npm:^0.6.0":
version: 0.6.0
resolution: "xml2js@npm:0.6.0"
dependencies:
sax: ">=0.6.0"
xmlbuilder: ~11.0.0
checksum: 437f353fd66d367bf158e9555a0625df9965d944e499728a5c6bc92a54a2763179b144f14b7e1c725040f56bbd22b0fa6cfcb09ec4faf39c45ce01efe631f40b
languageName: node
linkType: hard
"xml@npm:=1.0.1":
version: 1.0.1
resolution: "xml@npm:1.0.1"